③k8s部署应用的流程与管理
适用于大部分项目(大同小异)
一、 项目迁移到K8S平台是怎样的流程
image- 要以镜像作为交付对象,不再以jar包、war包形式
- 在docker可以直接上传镜像运行容器,在k8s需要编写yaml文件,通过控制器去管理镜像
- 部署完成后要将容器或pod暴露出去
- 例如前后端分离在k8s中是两个镜像,分别需要让外部用户能够访问到此pod或容器
- 对应用pod进行一下日志的采集和监控,方便做数据分析、历史资源利用率检查和告警等
镜像的分类
-
基础镜像:Docker Hub仓库上,CentOS,Ubuntu、Alpine
-
运行镜像:JDK、PHP、Java
-
项目镜像:将项目代码镜像打包到运行环境镜像中
二、 示例:在k8s中部署Java应用
学习案例使用为一位k8s大牛的案例
tomcat镜像包链接: https://pan.baidu.com/s/1v4EHVaSs2D38NhP0W5JE8A 提取码: n5qc
[root@k8s-master ~]# unzip tomcat-java-demo-master.zip
[root@k8s-master ~]# ll tomcat-java-demo-master
total 24
drwxr-xr-x 2 root root 34 Aug 5 2019 db #sql测试文件
-rw-r--r-- 1 root root 148 Aug 5 2019 Dockerfile #构建打包项目镜像
-rw-r--r-- 1 root root 11357 Aug 5 2019 LICENSE
-rw-r--r-- 1 root root 1930 Aug 5 2019 pom.xml #java编译环境
-rw-r--r-- 1 root root 270 Aug 5 2019 README.md
drwxr-xr-x 3 root root 18 Aug 5 2019 src #源码目录
提前准备一台数据库测试环境
# IP 192.168.0.40
[root@k8s-mariadb ~]# mysql --version
mysql Ver 15.1 Distrib 5.5.65-MariaDB, for Linux (x86_64) using readline 5.1
将sql测试数据导入到数据库中
scp -rp db/tables_ly_tomcat.sql root@192.168.0.40:~
#进入到数据库导入数据
MariaDB [test]> use test;
MariaDB [test]> source /root/tables_ly_tomcat.sql;
MariaDB [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| user |
+----------------+
1 row in set (0.00 sec)
#授权
MariaDB [test]> grant all on test.* to 'test'@'%' identified by '123456';
在其他节点上测试数据库是否可以连接
[root@k8s-node02 ~]# mysql -h192.168.0.40 -utest -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 9
Server version: 5.5.65-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test |
+--------------------+
2 rows in set (0.00 sec)
1. 下载java编译环境进行构建
yum install openjdk-1.8.0-java maven -y
mvn clean package -Dmaven.test.skup=true
#生成target目录,和下面的war包
[root@k8s-master tomcat-java-demo-master]# ls target/
classes ly-simple-tomcat-0.0.1-SNAPSHOT maven-archiver
generated-sources ly-simple-tomcat-0.0.1-SNAPSHOT.war maven-status
2. 修改连接数据库的配置
#mysql地址修改为提前准备的mysql测试环境IP,test用户和密码
[root@k8s-master tomcat-java-demo-master]# vim src/main/resources/application.yml
url: jdbc:mysql://192.168.0.40:3306/test?characterEncoding=utf-8
username: test
password: 123456
3. 重新构建
mvn clean package -Dmaven.test.skup=true
4. 查看Dcokerfile文件
[root@k8s-master tomcat-java-demo-master]# cat Dockerfile
FROM 245684979/tomcat #镜像
LABEL maintainer www.ctnrs.com #标签作者
RUN rm -rf /usr/local/tomcat/webapps/* #删除tomcat镜像中默认程序
ADD target/*.war /usr/local/tomcat/webapps/ROOT.war #添加war包,当前目录下生成的
5. 使用doclerfile构建镜像
# Dockerfile文件名格式可以识别所以不需要-f指定
[root@k8s-master tomcat-java-demo-master]# docker build -t 245684979/java-demo .
...
Successfully tagged 245684979/java-demo:latest
6. 推送到镜像仓库
#推送到DockerHub,需要登录账号
docker push 245684979/java-demo:latest
#建议搭建一个私有镜像仓库
7. 其他node节点可以提前pull镜像
docker pull 245684979/tomcat
8. master节点生成yaml文件
kubectl create deployment java-demo --image=245684979/java-demo --dry-run=client -o yaml >deploy.yaml
#进行修改,副本数改为3,删除一些多余项
[root@k8s-master ~]# cat deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: java-demo
name: java-demo
spec:
replicas: 3
selector:
matchLabels:
app: java-demo
template:
metadata:
labels:
app: java-demo
spec:
containers:
- image: 245684979/java-demo
name: java-demo
9. 创建pod
[root@k8s-master ~]# kubectl apply -f deploy.yaml
deployment.apps/java-demo created
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
java-demo-b57dd87fb-5rlsl 1/1 Running 0 65s
java-demo-b57dd87fb-hr4ch 1/1 Running 0 65s
java-demo-b57dd87fb-m5dzg 1/1 Running 0 65s
10. 检查状态
#现在只是pod的状态成功了,容器内是否成功可以通过日志查看某个pod
[root@k8s-master ~]# kubectl logs java-demo-b57dd87fb-5rlsl
...
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.1.RELEASE)
...
21-Jul-2020 10:55:51.099 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 26598 ms
11. 暴露应用端口
# --port:集群内部service之间访问端口
# --target-port:pod内应用程序访问端口
# --type=NodePort:生成集群外部访问端口
[root@k8s-master ~]# kubectl expose deployment java-demo --port=80 --target-port=8080 --type=NodePort -o yaml --dry-run >svc.yaml
[root@k8s-master ~]# cat svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: java-demo
name: java-demo
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: java-demo
type: NodePort
#构建svc
[root@k8s-master ~]# kubectl apply -f svc.yaml
service/java-demo created
#查看
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/java-demo NodePort 10.10.17.207 <none> 80:32433/TCP 72s
service/kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 3d17h
12. 进行访问暴露端口成功的应用
http://192.168.0.10:32433/
image
13. 进行添加数据
image
image
14. 在测试数据库中查看数据
MariaDB [test]> select * from user;
+----+-----------+-----+------+
| id | name | age | sex |
+----+-----------+-----+------+
| 1 | 高圆圆 | 18 | F |
| 2 | 蔡徐坤 | 20 | M |
| 3 | 刘芳 | 16 | F |
| 4 | 刘芳 | 18 | F |
| 5 | 貂蝉 | 800 | F |
| 6 | 范冰冰 | 40 | F |
| 7 | 金星 | 45 | Y |
+----+-----------+-----+------+
7 rows in set (0.00 sec)
三、 Pod对象的理解
1. Pod的基本概念:one:
-
最小的部署单元
-
一组容器的集合
-
一个Pod中的容器共享网络命名空间和存储
-
Pod是短暂的
2. Pod存在的意义:two:
-
Pod为亲密性应用而存在
-
亲密性应用场景:
- 两个应用直接发送文件交互
- 两个应用需要通过127.0.0.1或者socket通信
- 两个应用需要发生频发的调用
3. Pod实现机制:three:
- 共享网络
- 多个容器在一个网络命名空间里
- 共享存储
- 数据卷中存储临时数据、日志、业务数据
- emtmyDir,多个容器之间数据共享
测试Pod共享存储的机制
#编写yaml文件,一个读一个写,都挂载到/data目录下
[root@k8s-master ~]# cat emtydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: write
image: centos
command: ["bash","-c","for i in {1..100};do echo $i >> /data/hello;sleep 1;done"]
volumeMounts:
- name: data
mountPath: /data
- name: read
image: centos
command: ["bash","-c","tail -f /data/hello"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
#进行构建
[root@k8s-master ~]# kubectl apply -f emtydir.yaml
#访问Pod查看write写容器
[root@k8s-master ~]# kubectl exec -it my-pod -c write bash
[root@my-pod /ls /data/
hello
[root@my-pod /]# tail -f /data/hello
59
60
61
62
63
#访问Pod查看read读容器
[root@k8s-master ~]# kubectl logs my-pod -c read
1
2
3
...
97
98
99
100
4. Pod容器分类与设计模式:four:
- Infrastructure Container:基础容器
维护整个Pod网络空间 - InitContainers:初始化容器
先于业务容器开始执行 - Containers:业务容器
并行启动
Pod Template常用功能字段解析
- 变量
- 拉取镜像
- 资源限制
- 健康检查
5. 健康检查
探针的种类
livenessProbe:健康状态检查,周期性检查服务是否存活,检查结果失败,将重启容器
readinessProbe:可用性检查,周期性检查服务是否可用,不可用将从service的endpoints中移除
探针的检测方法
- exec:执行一段命令
- httpGet:检测某个 http 请求的返回状态码
- tcpSocket:测试某个端口是否能够连接
四、 Deployment 控制器(无状态应用)
Deployment
同样也是Kubernetes
系统的一个核心概念,主要职责和RC
一样的都是保证Pod
的数量和健康,二者大部分功能都是完全一致的,我们可以看成是一个升级版的RC
控制器。
Deployment
具备的新特性
-
RC
的全部功能:Deployment
具备上面描述的RC
的全部功能 - 事件和状态查看:可以查看
Deployment
的升级详细进度和状态 - 回滚:当升级
Pod
的时候如果出现问题,可以使用回滚操作回滚到之前的任一版本 - 版本记录:每一次对
Deployment
的操作,都能够保存下来,这也是保证可以回滚到任一版本的基础 - 暂停和启动:对于每一次升级都能够随时暂停和启动
Deployment
作为新一代的RC
,不仅在功能上更为丰富了,同时我们也说过现在官方也都是推荐使用Deployment
来管理Pod
的,比如一些官方组件kube-dns
、kube-proxy
也都是使用的Deployment
来管理的,所以最好使用Deployment
来管理Pod
。
可以看出一个Deployment拥有多个Replica Set,而一个Replica Set拥有一个或多个Pod。一个Deployment控制多个rs主要是为了支持回滚机制,每当Deployment操作时,Kubernetes会重新生成一个Replica Set并保留,以后有需要的话就可以回滚至之前的状态。
Replica Set的作用就是副本数记录和历史版本记录
1. Pod 与 controllers 的关系
controllers:在集群上管理和运行容器的对象
通过label-selector
相关联
Pod通过控制器实现应用的运维,如伸缩,滚动升级等
2. Deployment功能与应用场景
部署"无状态应用"
管理Pod和ReplicaSet
具有上线部署、副本设定、滚动升级、回滚等功能
提供声明式更新,例如只更新一个新的 image
应用场景:Web服务,微服务
3. YAML字段解析
image4. 使用Deployment部署一个Java应用
4.1 创建
生成yaml文件
kubectl create deployment web --image=245684979/java-demo --dry-run -o yaml >web.yaml
修改yaml文件
#使用准备好的docker hub上的java镜像:245684979/java-demo
#修改为3个副本
[root@k8s-master ~]# cat web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- image: 245684979/java-demo
name: java
构建yaml
[root@k8s-master ~]# kubectl apply -f web.yaml
deployment.apps/web created
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
java-demo-b57dd87fb-5rlsl 1/1 Running 1 23h
java-demo-b57dd87fb-cp2tq 1/1 Running 1 23h
java-demo-b57dd87fb-shr4k 1/1 Running 1 23h
web-5ccd9ffd6-694m2 1/1 Running 0 51s
web-5ccd9ffd6-llq9z 1/1 Running 0 51s
web-5ccd9ffd6-tlh9q 1/1 Running 0 51s
4.2 发布
通过service暴露端口
[root@k8s-master ~]# kubectl expose deployment web --port=80 --target-port=8080 --type=NodePort
service/web exposed
#如果要生成yaml文件后面添加 --dry-run=client -o yaml >web_service.yaml
查看service
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
java-demo NodePort 10.10.17.207 <none> 80:32433/TCP 24h
kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 4d17h
web NodePort 10.10.9.46 <none> 80:32683/TCP 2m36s
进行访问
image数据库的配置已经打包到镜像中了,所以之前的数据也存在
5. 升级与回滚
5.1 升级
升级为nginx镜像
#做测试修改为nginx的pod
[root@k8s-master ~]# kubectl set image deployment web java=nginx
deployment.apps/web image updated
#默认为滚动更新,启动一台新pod,删除一台旧pod,直到全部替换更新
进行访问会发现无法访问
需要修改service的端口为nginx的80
[root@k8s-master ~]# kubectl edit service web
...
targetPort: 80
再次访问已经为nginx的首页了
查看升级状态
[root@k8s-master ~]# kubectl rollout status deployment web
deployment "web" successfully rolled out
5.2 回滚
查看历史版本记录
[root@k8s-master ~]# kubectl rollout history deployment web
deployment.apps/web
REVISION CHANGE-CAUSE
1 <none>
2 <none>
回滚到上一版本
[root@k8s-master ~]# kubectl rollout undo deployment web
deployment.apps/web rolled back
会发现访问网页提示无法访问了,说明回到了未修改端口的上一版本
image
#修改端口为8080
[root@k8s-master ~]# kubectl edit service web
...
targetPort: 8080
image
回滚到指定版本的参数
kubectl rollout undo deployment web --to-revision=2
6. 弹性伸缩 scale
扩容和缩容一样的操作,只需要修改副本数
#将web扩容为5个副本,会在之前的3个之上再添加2个pod
[root@k8s-master ~]# kubectl scale deployment web --replicas=5
deployment.apps/web scaled
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
java-demo-b57dd87fb-5rlsl 1/1 Running 1 27h
java-demo-b57dd87fb-cp2tq 1/1 Running 1 26h
java-demo-b57dd87fb-shr4k 1/1 Running 1 27h
web-64c686b49d-28zbv 0/1 ContainerCreating 0 3s
web-64c686b49d-cb65c 0/1 ContainerCreating 0 3s
web-64c686b49d-479qh 1/1 Running 0 7m11s
web-64c686b49d-s95bb 1/1 Running 0 4m52s
web-64c686b49d-vbbnx 1/1 Running 0 6m49s
#缩容为3个副本
kubectl scale deployment web --replicas=3
#也可以直接去修改副本数
kubectl edit deployment web
k8s的扩容只是一个维度,需要建立在集群的每个节点资源之上。根据业务的量去扩容缩容。
在业务高峰或者低峰的时候,可以手动的调整Pod
数量来提供资源的利用率,也可以使用HPA
这种资源对象,可以做到自动伸缩。
五、 Service对象
Pod
的生命是有限的,死亡过后不会复活了。RC
和Deployment
可以用来动态的创建和销毁Pod
。尽管每个Pod
都有自己的IP
地址,但是如果Pod
重新启动了的话那么它的IP
很有可能也就变化了。这就会带来一个问题:比如我们有一些后端的Pod
的集合为集群中的其他前端的Pod
集合提供API
服务,如果我们在前端的Pod
中把所有的这些后端的Pod
的地址都写死了,然后去某种方式去访问其中一个Pod
的服务,这样看上去是可以正常工作的,但是如果这个Pod
挂掉了,然后重新启动起来了,IP
地址非常有可能就变了,这个时候前端就极大可能访问不到后端的服务了。
遇到这样的问题该怎么解决呢?:question:
-
比如我们在部署一个
WEB
服务的时候,前端一般部署一个Nginx
作为服务的入口,然后Nginx
后面肯定就是挂载的其他后端的各种服务,以前我们可能是去手动更改Nginx
配置中的资源池upstream
选项,来动态改变提供服务的数量 -
如果要解决上面遇到的问题,可以通过服务发现的工具解决掉。当
Pod
被销毁或者新建过后,我们可以把这个Pod
的地址注册到这个服务发现中心去就可以。但是这样的话我们的前端的Pod
结合就不能直接去连接后台的Pod
集合了,所以还应该连接到一个能够做服务发现的中间件上面。 -
Kubernetes
集群就为我们提供了这样的一个对象 -Service
,Service
是一种抽象的对象,它定义了一组Pod
的逻辑集合和一个用于访问它们的策略,这个概念和微服务非常类似。一个Serivce
下面包含的Pod
集合一般是由Label Selector
(标签)来决定的。 -
上面举的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的
Pod
集合会发送变化,但是前端的Pod
却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service
的这种抽象对象就可以帮我们达到这种解耦的目的。
1. Kubernetes的三种IP
- Node IP:
Node
节点的IP
地址 - Pod IP:
Pod
的IP地址 - Cluster IP:
Service
的IP
地址
:one:首先,Node IP
是Kubernetes
集群中节点的物理网卡doIP
地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以Kubernetes
集群外要想访问Kubernetes
集群内部的某个节点或者服务,肯定得通过Node IP
进行通信(这个时候一般是通过外网IP
了)
:two:然后Pod IP
是每个Pod
的IP
地址,它是Docker Engine
根据docker0
网桥的IP
地址段进行分配的。
:three:最后Cluster IP
是一个虚拟的IP
,仅仅作用于Kubernetes Service
这个对象,由Kubernetes
自己来进行管理和分配地址,当然我们也无法ping
这个地址,他没有一个真正的实体对象来响应,他只能结合Service Port
来组成一个可以通信的服务。
2. Service存在的意义
- 防止Pod失联(服务发现)
- 定义一组Pod的访问策略(负载均衡),service默认使用iptables来实现负载均衡。
3. Pod与Service的关系
- 通过
Label Selector
(标签)相关联 - 通过Service实现Pod的四层负载均衡 传输层tcp,udp)
#查看之前创建的java-demo的yaml文件
[root@k8s-master ~]# cat deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: java-demo
name: java-demo
spec:
replicas: 3
selector: #定义了标签为java-demo
matchLabels:
app: java-demo
template:
metadata:
labels:
app: java-demo
spec:
containers:
- image: 245684979/java-demo
name: java-demo
通过的使用kubectl apply -f deploy.yaml
就可以创建一个名为java-demo
的Service
对象,具有标签app=java-demo
的Pod
上,这个Service
会被系统分配一个Cluster IP
,该Service
还会持续的监听selector
下面的Pod
,会把这些Pod
信息更新到一个名为java-demo
的endpoints
对象上去,这个对象就类似于我们上面说的Pod
集合。
[root@k8s-master ~]# kubectl get endpoints
NAME ENDPOINTS AGE
java-demo 10.244.58.211:8080,10.244.85.210:8080,10.244.85.211:8080 28h
kubernetes 192.168.0.10:6443 4d22h
web 10.244.58.221:8080,10.244.85.228:8080,10.244.85.232:8080 4h23m
4. kube-proxy与Service的关系
负责为 Service 提供 cluster 内部的服务发现和负载均衡
Kubernetes
中默认是使用的iptables
这种模式来代理
kube-proxy
会监视Kubernetes master
对 Service 对象和 Endpoints 对象的添加和移除。
对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求。
image
5. Service类型
在定义Service
的时候可以指定一个自己需要的类型的Service
,如果不指定的话默认是ClusterIP
类型。
可以使用的四种服务类型
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的服务类型。
- NodePort:通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 ,可以从集群的外部访问一个 NodePort 服务。
- LoadBalancer:使用公有云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。
-
ExternalName:不常用, 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。
image
6. 创建ClusterIP类型的Service
之前已经创建了NodePort
类型的service
再创建一个新的service
类型svc2.yaml
文件来测试ClusterIP
# --port:集群内部service之间访问端口
# --target-port:pod内应用程序访问端口
# --type=NodePort:生成集群外部访问端口
kubectl expose deployment java-demo --port=80 --target-port=8080 --type=ClusterIP --dry-run -o yaml >svc2.yaml
#删除之前的,构建新创建的
[root@k8s-master ~]# kubectl delete -f svc.yaml
[root@k8s-master ~]# kubectl apply -f svc2.yaml
#查看svc
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
java-demo ClusterIP 10.10.154.28 <none> 80/TCP 2m
kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 5d
web NodePort 10.10.9.46 <none> 80:32683/TCP 6h36m
#可以直接访问集群内部IP了
curl 10.10.154.28
....
whatToType: "这里有很多美女,挑一个回家吧!",
typeSpeed: 300,
lifeLike: true,
breakLines :true
}, function() {
console.log('This is tomcat callback function!');
});
</script>
</body>
</html>
7. ipvs负载均衡替代iptables
service多了以后 iptables表会影响性能
支持多种调度算法
生产中使用ipvs来做service的负载均衡转发
六、 ingress 实现集群外部的服务发现
Kubernetes 集群中的应用如何暴露给外部的用户使用呢?:question:
- 使用
NodePort
和LoadBlancer
类型的Service
可以实现把应用暴露给外部用户使用,除此之外,Kubernetes 还为我们提供了一个非常重要的资源对象可以用来暴露服务给外部用户,那就是ingress
。 - 对于小规模的应用我们使用 NodePort 或许能够满足我们的需求,但是当你的应用越来越多的时候,你就会发现对于
NodePort
的管理就非常麻烦了,这个时候使用ingress
就非常方便了,可以避免管理大量的 Port。 - Ingress为解决NodePort的痛点而生。
1. 简介
vivo 公司 Kubernetes 集群 Ingress 网关实践
推荐阅读学习ingress的文章
Ingress
其实就是从 kuberenets 集群外部访问集群的一个入口,将外部的请求转发到集群内不同的 Service 上,其实就相当于 nginx、haproxy 等负载均衡代理服务器,会想到直接使用 nginx 就实现了,但是只使用 nginx 这种方式有很大缺陷,每次有新服务加入的时候怎么改 Nginx 配置?不可能去手动更改或者滚动更新前端的 Nginx 的Pod 。
如果再加上一个服务发现的工具的话其实就可以了,Ingress 实际上就是这样实现的,只是服务发现的功能自己实现了,不需要使用第三方的服务了,然后再加上一个域名规则定义,路由信息的刷新需要一个靠 Ingress controller 来提供。
Ingress controller 可以理解为一个监听器,通过不断地与 kube-apiserver 打交道,实时的感知后端 service、pod 的变化,当得到这些变化信息后,Ingress controller 再结合 Ingress 的配置,更新反向代理负载均衡器,达到服务发现的作用。
现在可以供大家使用的 Ingress controller 有很多,比如 traefik、nginx-controller、Kubernetes Ingress Controller for Kong、HAProxy Ingress controller,当然你也可以自己实现一个 Ingress Controller,现在普遍用得较多的是 traefik 和 nginx-controller,traefik 的性能较 nginx-controller 差,但是配置使用要简单许多。
2. 各种 Ingress 横向对比
image2. Pod与Ingress的关系
- 通过Service关联Pod
- 基于域名访问
- 通过Ingress Controller实现Pod的负载均衡
- 支持TCP/UDP 4层和HTTP 7层
3. Ingress Nginx的工作原理分析
Nginx-ingress
是 Kubernetes
生态中的重要成员,主要负责向外暴露服务,同时提供负载均衡等附加功能;
nginx-ingress
已经能够完成 7/4 层的代理功能;4层代理基于 ConfigMap
。
1. Nginx 的 7 层反向代理模式
imageNginx 对后端运行的服务(Service1、Service2)提供反向代理,在配置文件中配置了域名与后端服务 Endpoints集合的对应关系。客户端通过使用 DNS 服务或者直接配置本地的 hosts 文件,将域名都映射到 Nginx 代理服务器。当客户端访问 service1.com 时,浏览器会把包含域名的请求发送给 nginx 服务器,nginx 服务器根据传来的域名,选择对应的 Service,这里就是选择 Service 1 后端服务,然后根据一定的负载均衡策略,选择 Service1 中的某个容器接收来自客户端的请求并作出响应。过程很简单,nginx 在整个过程中仿佛是一台根据域名进行请求转发的“路由器”,这也就是7层代理的整体工作流程。
对于 Nginx 反向代理做了什么,上面说的内容已经大概了解了。在 k8s 中,后端服务的变化是十分频繁的,单纯依靠人工来更新nginx 的配置文件几乎不可能,nginx-ingress 由此应运而生。Nginx-ingress 通过监视 k8s 的资源状态变化实现对 nginx 配置文件的自动更新
2. nginx-ingress 工作流程分析
imagenginx-ingress 模块在运行时主要包括三个主体:NginxController、Store、SyncQueue。其中,Store 主要负责从 kubernetes APIServer 收集运行时信息,感知各类资源(如 ingress、service等)的变化,并及时将更新事件消息(event)写入一个环形管道;SyncQueue 协程定期扫描 syncQueue 队列,发现有任务就执行更新操作,即借助 Store 完成最新运行数据的拉取,然后根据一定的规则产生新的 nginx 配置,(有些更新必须 reload,就本地写入新配置,执行 reload),然后执行动态更新操作,即构造 POST 数据,向本地 Nginx Lua 服务模块发送 post 请求,实现配置更新;NginxController 作为中间的联系者,监听 updateChannel,一旦收到配置更新事件,就向同步队列 syncQueue 里写入一个更新请求。
当添加一个web服务 wordpress 后,在 nginx 配置中会添加一个 server 条目,但该条目中没有具体指出后端的容器地址,而是指向了一个叫 http://upstream_balancer 的地址,这个 balancer 其实是由 Lua 动态提供路由的。既然没有实际的容器后端在配置文件中进行配置,自然地,服务中容器数量的增减变化也就不必修改 nginx 配置文件了,这就是免 reload 的关键!简单推测,Lua 模块所做的就是维持一个服务到容器的映射关系,动态地提供负载均衡路由。
4. 部署ingress-nginx Controller
https://github.com/kubernetes/ingress-nginx/
下载官方yaml文件
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/cloud/deploy.yaml
#这一步为可选项:修改为 DaemonSet 控制器,特性是在每个node节点运行一个同样的Pod
#默认为 Deployment
apiVersion: apps/v1
kind: DaemonSet
#修改镜像地址为国内(阿里云仅支持到0.32版)
[root@k8s-master ingress-nginx]# vim deploy.yaml
...
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.32.0
imagePullPolicy: IfNotPresent
构建ingress nginx
kubectl apply -f deploy.yaml
[root@k8s-master ingress-nginx]# kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-wjnhs 0/1 Completed 0 88s
ingress-nginx-admission-patch-rtg6c 0/1 Completed 0 88s
ingress-nginx-controller-574f54bc47-vp2fs 1/1 Running 0 99s
查看ingress的pod和版本
[root@k8s-master ingress-nginx]# kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-dqg8l 0/1 Completed 0 6m58s
ingress-nginx-admission-patch-tj2js 0/1 Completed 0 6m58s
ingress-nginx-controller-574f54bc47-qzjmv 1/1 Running 0 7m8s
[root@k8s-master ingress-nginx]# kubectl exec -it -n ingress-nginx ingress-nginx-controller-574f54bc47-qzjmv -- /nginx-ingress-controller --version
-------------------------------------------------------------------------------
NGINX Ingress controller
Release: 0.32.0
Build: git-446845114
Repository: https://github.com/kubernetes/ingress-nginx
nginx version: nginx/1.17.10
-------------------------------------------------------------------------------
5. 创建ingress规则
[root@k8s-master ingress-nginx]# cat ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: web
spec:
rules:
- host: blog.girl.com
http:
paths:
- backend:
serviceName: web
servicePort: 80
#查看当前service,这里使用web的的80端口
[root@k8s-master ingress-nginx]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
java-demo NodePort 10.10.206.186 <none> 80:31985/TCP 23h
kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 5d23h
web NodePort 10.10.9.46 <none> 80:32683/TCP 30h
#构建ingress
[root@k8s-master ingress-nginx]# kubectl apply -f ingress.yaml
ingress.extensions/web created
[root@k8s-master ingress-nginx]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
web <none> blog.girl.com 80 11s
在本地配置hosts解析
在本地进行ping是否畅通
浏览器使用域名访问
如果想不使用端口访问需要在web的pod中添加hostPort端口,因为现在是NodePort端口
6. 示例Nginx服务负载均衡测试
#创建nginx的pod
[root@k8s-master pod]# cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
[root@k8s-master pod]# kubectl apply -f nginx.yaml
[root@k8s-master pod]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-f89759699-5bn67 1/1 Running 0 8m
nginx-f89759699-h9jqt 1/1 Running 0 7m
nginx-f89759699-m2kgt 1/1 Running 0 7m
#创建service
[root@k8s-master service]# cat svc_nginx.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: NodePort
[root@k8s-master pod]# kubectl apply -f svc_nginx.yaml
#创建ingress
[root@k8s-master ingress-nginx]# cat ingress_nginx.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
spec:
rules:
- host: www.nginx.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
[root@k8s-master pod]# kubectl apply -f ingress_nginx.yaml
[root@k8s-master ingress-nginx]# kubectl get svc,ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx NodePort 10.10.121.138 <none> 80:32645/TCP 8m
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.extensions/nginx <none> www.nginx.com 80 3m
#修改Pod中的三台容器的首页index.html文件
[root@k8s-master ingress-nginx]# kubectl exec -it nginx-f89759699-5bn67 bash
root@nginx-f89759699-5bn67:/# echo nginx-f89759699-5bn67 >/usr/share/nginx/html/index.html
[root@k8s-master ingress-nginx]# kubectl exec -it nginx-f89759699-h9jqt /bin/bash
root@nginx-f89759699-h9jqt:/# echo nginx-f89759699-h9jqt >/usr/share/nginx/html/index.htm
[root@k8s-master ingress-nginx]# kubectl exec -it nginx-f89759699-m2kgt /bin/bash
root@nginx-f89759699-m2kgt:/# echo nginx-f89759699-m2kgt >/usr/share/nginx/html/index.html
添加本地hosts解析
浏览器访问并刷新页面查看负载均衡的效果
image
七、Helm包管理工具
官网: https://helm.sh/
1. Helm作用与概念
Helm是个啥?
Helm
就相当于kubernetes
环境下的yum
包管理工具。包管理器类似于我们在 Ubuntu 中使用的apt、Centos中使用的yum 或者Python中的 pip 一样,能快速查找、下载和安装软件包。Helm 由客户端组件 helm 和服务端组件 Tiller 组成, 能够将一组K8S资源打包统一管理, 是查找、共享和使用为Kubernetes构建的软件的最佳方式。
Helm的用途
做为 Kubernetes 的一个包管理工具,Helm
具有如下功能:
- 创建新的 chart
- chart 打包成 tgz 格式
- 上传 chart 到 chart 仓库或从仓库中下载 chart
- 在
Kubernetes
集群中安装或卸载 chart - 管理用
Helm
安装的 chart 的发布周期
Helm相关组件及概念
Helm 包含两个组件,分别是 helm 客户端 和 Tiller 服务器:
- helm 是一个命令行工具,用于本地开发及管理chart,chart仓库管理等
- Tiller 是 Helm 的服务端。在V3版本被弃用。Tiller 负责接收 Helm 的请求,与 k8s 的 apiserver 交互,根据chart 来生成一个 release 并管理 release
- chart Helm的打包格式叫做chart,所谓chart就是一系列 yaml 文件, 它描述了一组相关的 k8s 集群资源
- release 使用 helm install 命令在 Kubernetes 集群中部署的 Chart 称为 Release
- Repoistory Helm chart 的仓库,Helm 客户端通过 HTTP 协议来访问存储库中 chart 的索引文件和压缩包
2. Helm的v3和v2版本变化
Helm3是CLI工具的最新主要版本。Helm 3基于Helm 2的成功,不断满足不断发展的生态系统的需求。Helm 3的内部实现已从Helm 2发生了很大变化。
https://github.com/helm/helm/releases/tag/
- 架构上的改变
- 最明显的变化删除了
Tiller
- Helm直接通过kubeconfig连接apiserver
- release名称可以在不同命名空间重用
- chart支持放到doceker镜像仓库
3. Helm安装
1. 下载Helm包
https://get.helm.sh/helm-v3.2.4-linux-amd64.tar.gz
[root@k8s-master tools]# wget https://get.helm.sh/helm-v3.2.4-linux-amd64.tar.gz
[root@k8s-master tools]# tar xf helm-v3.2.4-linux-amd64.tar.gz
[root@k8s-master tools]# ls linux-amd64/
helm LICENSE README.md
[root@k8s-master tools]# mv linux-amd64/helm /usr/bin/
2. 配置Helm国内镜像源
阿里云的源已经不更新了,这里使用微软的国内源 http://mirror.azure.cn/kubernetes/charts
[root@k8s-master ~]# helm repo add stable http://mirror.azure.cn/kubernetes/charts
"stable" has been added to your repositories
[root@k8s-master ~]# helm repo list
NAME URL
stable http://mirror.azure.cn/kubernetes/charts
3. 设置 Helm 命令自动补齐
source <(helm completion bash)
4. Helm的测试使用
1. 搜索一个weave的包
weave是k8s的一个ui界面
[root@k8s-master ~]# helm search repo weave
NAME CHART VERSION APP VERSION DESCRIPTION
stable/weave-cloud 0.3.7 1.4.0 Weave Cloud is a add-on to Kubernetes which pro...
stable/weave-scope 1.1.10 1.12.0 A Helm chart for the Weave Scope cluster visual...
2. 下载包
[root@k8s-master ~]# helm install ui stable/weave-scope
NAME: ui
LAST DEPLOYED: Mon Jul 27 10:48:59 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
You should now be able to access the Scope frontend in your web browser, by
using kubectl port-forward:
kubectl -n default port-forward $(kubectl -n default get endpoints \
ui-weave-scope -o jsonpath='{.subsets[0].addresses[0].targetRef.name}') 8080:4040
then browsing to http://localhost:8080/.
For more details on using Weave Scope, see the Weave Scope documentation:
https://www.weave.works/docs/scope/latest/introducing/
3. 查看安装好的release、Pod、Service
[root@k8s-master ~]# helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
ui default 1 2020-07-27 10:48:59.644952211 +0800 CST deployed weave-scope-1.1.10 1.12.0
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
weave-scope-agent-ui-khzbl 1/1 Running 0 4m25s
weave-scope-agent-ui-x9kgm 1/1 Running 0 4m25s
weave-scope-cluster-agent-ui-7498b8d4f4-w7x94 1/1 Running 0 4m25s
weave-scope-frontend-ui-649c7dcd5d-rl2m4 1/1 Running 0 4m25s
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ui-weave-scope ClusterIP 10.10.97.176 <none> 80/TCP 6m12s
4. 将service修改为NodePort暴露给外部
#将ClusterIP修改为NodePort
[root@k8s-master ~]# kubectl edit service ui-weave-scope
...
sessionAffinity: None
type: NodePort
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ui-weave-scope NodePort 10.10.97.176 <none> 80:32328/TCP 14m
5. 访问
http://192.168.0.10:32328
image
可以直接登录控制台
image
5. Helm的模板创建
1. 手动创建一个 Chart
其中最核心的是 templates 这个文件夹,里面其实就是 K8s 资源描述yaml文件的模版。模版里面的内容可以通过 values.yaml
里面的内容去渲染,同时也可以在使用 helm install --set key=value xxx
部署的时候去覆盖 values.yaml
里面的默认值。
[root@k8s-master helm]# helm create mychart
Creating mychart
[root@k8s-master helm]# tree mychart/
mychart/
├── charts
├── Chart.yaml
├── templatesll
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt
│ ├── serviceaccount.yaml
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
3 directories, 10 files
#先将默认生成的文件删除掉
[root@k8s-master helm]# cd mychart/templates/
[root@k8s-master templates]# rm -rf *
2. 将之前创建的nginx的yaml文件复制进来
cp /root/service/svc_nginx.yaml /root/deployment/nginx.yaml ./
mv nginx.yaml deployment.yaml
[root@k8s-master templates]# ls
deployment.yaml svc_nginx.yaml
3. 进行安装nginx模板
如果有之前创建相同名字的资源,需要删除掉,否则会报错
[root@k8s-master ~]# helm install nginx helm/mychart/
NAME: nginx
LAST DEPLOYED: Mon Jul 27 14:06:43 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-f89759699-f2gg5 1/1 Running 0 45s
nginx-f89759699-fqrpx 1/1 Running 0 45s
nginx-f89759699-kmrlg 1/1 Running 0 45s
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
nginx NodePort 10.10.182.160 <none> 80:32580/TCP 9s
ui-weave-scope NodePort 10.10.97.176 <none> 80:32328/TCP 3h17m
4. 查看已经创建的模板
[root@k8s-master ~]# helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
nginx default 1 2020-07-27 14:06:43.23384726 +0800 CST deployed mychart-0.1.0 1.16.0
ui default 1 2020-07-27 10:48:59.644952211 +0800 CST deployed weave-scope-1.1.10 1.12.0
5. helm模板的升级
#如果yaml文件中有修改,修改后需要执行upgrade
[root@k8s-master ~]# helm upgrade nginx helm/mychart/
Release "nginx" has been upgraded. Happy Helming!
NAME: nginx
LAST DEPLOYED: Mon Jul 27 14:14:42 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
6. Helm Chart开发与模板使用
一套yaml文件部署多个应用时,yaml文件需要修改的位置
- 资源名称
- 镜像
- 标签
- 副本数
- 端口
1. 动态的渲染模板
[root@k8s-master mychart]# pwd
/root/helm/mychart
#删除默认内容,添加下面的基础信息用来测试
[root@k8s-master mychart]# cat values.yaml
replicas: 1
image: 245684979/java-demo
tag: latest
label: java-demo
port: 8080
2. 修改nginx模板的yaml文件
[root@k8s-master mychart]# cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-dp #-dp 标识符
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Values.label }}
template:
metadata:
labels:
app: {{ .Values.label }}
spec:
containers:
- image: {{ .Values.image }}:{{ .Values.tag }}
name: nginx
###
[root@k8s-master ~]# cat helm/mychart/templates/svc_nginx.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-svc #标识符
spec:
ports:
- port: 80
protocol: TCP
targetPort: {{ .Values.port }}
selector:
app: {{ .Values.label }}
type: NodePort
3. 将渲染变量模板应用到yaml中
输出一下变量渲染生效后的输出结果
# --dry-run: 不执行查看渲染结果,如果不加此参数则会创建
[root@k8s-master ~]# helm install --dry-run nginx helm/mychart/
NAME: nginx
LAST DEPLOYED: Mon Jul 27 14:52:55 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: mychart/templates/svc_nginx.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: java-demo
type: NodePort
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dp
spec:
replicas: 1
selector:
matchLabels:
app: java-demo
template:
metadata:
labels:
app: java-demo
spec:
containers:
- image: 245684979/java-demo:latest
name: nginx
执行此模板
[root@k8s-master ~]# helm install java-demo helm/mychart/
NAME: java-demo
LAST DEPLOYED: Mon Jul 27 14:56:53 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
查看生成的pod和service
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
...
java-demo-dp-859745b69c-2r78f 1/1 Running 0 101s
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
java-demo-svc NodePort 10.10.217.33 <none> 80:31618/TCP 2m3s
...
也可以尝试访问一下是否成功
http://192.168.0.10:31618
image
4. 更新chart包
[root@k8s-master ~]# helm upgrade java-demo --set replicas=3 helm/mychart/
Release "java-demo" has been upgraded. Happy Helming!
NAME: java-demo
LAST DEPLOYED: Mon Jul 27 15:08:34 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
java-demo-dp-859745b69c-2r78f 1/1 Running 0 12m
java-demo-dp-859745b69c-gzx8k 1/1 Running 0 46s
java-demo-dp-859745b69c-zxvwl 1/1 Running 0 46s
5. 回滚chart包
#列出模板的历史版本信息
[root@k8s-master ~]# helm history java-demo
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Jul 27 14:56:53 2020 superseded mychart-0.1.0 1.16.0 Install complete
2 Mon Jul 27 15:08:34 2020 deployed mychart-0.1.0 1.16.0 Upgrade complete
#回滚到 1 版本,1版本为创第一次创建,副本数为1;创建完成后可以检查一下
[root@k8s-master ~]# helm rollback java-demo 1
Rollback was a success! Happy Helming!
#由三副本变为1副本了
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
java-demo-dp-859745b69c-2r78f 1/1 Running 0 17m
7. Chart 的UI管理界面 monocular
可以在里面添加 chart 的仓库,能够查看到当前集群中已经运行的 Chart,同时还能够直接在页面上就部署一个 chart 至 kuberntes 集群,就好像一个 kubernetes 的 appstore 一样。不支持覆盖 chart 中指定一个的默认参数,唯一可选择的就是部署的 namespace。
安装方法比较简单 可访问官方查看:https://github.com/helm/monocular
8. Helm命令总结
#搜索可用于安装的Chart
helm search mysql
#安装一个Chart
helm install stable/mysql
#列出k8s中已经部署的Chart
helm list --all
#helm的仓库更新
helm repo update
#查看镜像源
helm repo list
#添加镜像源
helm repo add stable <url>
#删除镜像源
helm repo remove <url>
#创建Chart包管理模板,会产生一个Chart所需的目录结构
helm create mychart
#升级chart包
helm upgrade --set xxx java-demo
#查看历史版本
helm history java-demo
#回滚chart包版本
helm rollback java-demo 1
#删除release
helm delete nginx
#打包Chart
helm package helm/mychart/
Successfully packaged chart and saved it to: /root/mychart-0.1.0.tgz
八、弹性伸缩(扩容/缩容)
1. Node扩容/缩容
从Kubernetes集群中删除节点的方法:https://blog.csdn.net/erhaiou2008/article/details/104986006/
1、Cluster AutoScaler
扩容: Cluster AutoScaler 定期检测是否有充足的资源来调度新创建的 Pod,当资源不足时会调用 Cloud Provider 创建新的 Node。
图片来自网络 image缩容: Cluster AutoScaler 也会定期监测 Node 的资源使用情况,当一个 Node 长时间资源利用率都很低时(低于 50%)自动将其所在虚拟机从云服务商中删除。此时,原来的 Pod 会自动调度到其他 Node 上面。
图片来自网络 image支持的云提供商:
- 阿里云:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/alicloud/README.md
- AWS: https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
- Azure: https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md
2. Pod自动扩容/缩容(HPA)
Horizontal Pod Autoscaler(HPA,Pod水平自动伸缩),根据资源利用率或者自定义指标自动调整replication controller, deployment 或 replica set,实现部署的自动扩展和缩减,让部署的规模接近于实际服务的负载。HPA不适于无法缩放的对象,例如DaemonSet。
1、HPA基本原理
Kubernetes 中的 Metrics Server 持续采集所有 Pod 副本的指标数据。HPA 控制器通过 Metrics Server 的 API(Heapster 的 API 或聚合 API)获取这些数据,基于用户定义的扩缩容规则进行计算,得到目标 Pod 副本数量。当目标 Pod 副本数量与当前副本数量不同时,HPA 控制器就向 Pod 的副本控制器(Deployment、RC 或 ReplicaSet)发起 scale 操作,调整 Pod 的副本数量,完成扩缩容操作。如图所示。
在弹性伸缩中,冷却周期是不能逃避的一个话题, 由于评估的度量标准是动态特性,副本的数量可能会不断波动。有时被称为颠簸, 所以在每次做出扩容缩容后,冷却时间是多少。
在 HPA 中,默认的扩容冷却周期是 3 分钟,缩容冷却周期是 5 分钟。
可以通过调整kube-controller-manager组件启动参数设置冷却时间:
- --horizontal-pod-autoscaler-downscale-delay :扩容冷却
- --horizontal-pod-autoscaler-upscale-delay :缩容冷却
2、HPA的演进历程
目前 HPA 已经支持了 autoscaling/v1
、autoscaling/v2beta1
和 autoscaling/v2beta2
三个大版本 。
目前大多数人比较熟悉是autoscaling/v1,这个版本只支持CPU一个指标的弹性伸缩。
而autoscaling/v2beta1增加了支持自定义指标,autoscaling/v2beta2又额外增加了外部指标支持。
而产生这些变化不得不提的是Kubernetes社区对监控与监控指标的认识与转变。从早期Heapster到Metrics Server再到将指标边界进行划分,一直在丰富监控生态。
3. 基于CPU指标进行缩放
1、 Kubernetes API Aggregation
在 Kubernetes 1.7 版本引入了聚合层,允许第三方应用程序通过将自己注册到kube-apiserver上,仍然通过 API Server 的 HTTP URL 对新的 API 进行访问和操作。为了实现这个机制,Kubernetes 在 kube-apiserver 服务中引入了一个 API 聚合层(API Aggregation Layer),用于将扩展 API 的访问请求转发到用户服务的功能。
图片来自网络
image
当你访问 apis/metrics.k8s.io/v1beta1 的时候,实际上访问到的是一个叫作 kube-aggregator 的代理。而 kube-apiserver,正是这个代理的一个后端;而 Metrics Server,则是另一个后端 。通过这种方式,我们就可以很方便地扩展 Kubernetes 的 API 了。
因是使用kubeadm部署的,默认已开启。如果使用二进制方式部署的话,需要在kube-APIServer中添加启动参数,增加以下配置:
# vim /opt/kubernetes/cfg/kube-apiserver.conf
...
--requestheader-client-ca-file=/opt/kubernetes/ssl/ca.pem \
--proxy-client-cert-file=/opt/kubernetes/ssl/server.pem \
--proxy-client-key-file=/opt/kubernetes/ssl/server-key.pem \
--requestheader-allowed-names=kubernetes \
--requestheader-extra-headers-prefix=X-Remote-Extra- \
--requestheader-group-headers=X-Remote-Group \
--requestheader-username-headers=X-Remote-User \
--enable-aggregator-routing=true \
...
设置完成重启 kube-apiserver 服务,就启用 API 聚合功能了。
2. autoscaling/v1(CPU指标测试)
autoscaling/v1版本只能支持CPU一个指标。
先部署一个应用pod
[root@k8s-master autoscaling]# cat deployment_web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: nginx-php
template:
metadata:
labels:
app: nginx-php
spec:
containers:
- image: 245684979/nginx-php
name: java
resources:
requests:
memory: "300Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-php
[root@k8s-master autoscaling]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-5bf5bb785f-tnjzn 1/1 Running 0 46s
#ClusterIP为10.244.58.206 接 -o wide参数查看
创建HPA策略组
[root@k8s-master autoscaling]# cat autoscaling_v1.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
maxReplicas: 5
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
targetCPUUtilizationPercentage: 60
#- scaleTargetRef:表示当前要伸缩对象是谁
#- targetCPUUtilizationPercentage:当整体的资源利用率超过60%的时候,会进行扩容
[root@k8s-master autoscaling]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web Deployment/web 0%/60% 1 1 1 1m
进行压测
yum install httpd-tools
ab -n 100000 -c 100 http://10.244.58.206/status.php
检查扩容状态
[root@k8s-master ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web Deployment/web 537%/60% 1 5 4 3m42s
[root@k8s-master ~]# kubectl top pods
NAME CPU(cores) MEMORY(bytes)
web-5bf5bb785f-tnjzn 1343m 25Mi
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-5bf5bb785f-7l5tl 0/1 ContainerCreating 0 41s
web-5bf5bb785f-7n886 1/1 Running 0 56s
web-5bf5bb785f-pzgzs 1/1 Running 0 56s
web-5bf5bb785f-qm8wb 0/1 ContainerCreating 0 56s
web-5bf5bb785f-tnjzn 1/1 Running 0 6m13s
关闭压测,过5分钟检查缩容状态
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-5bf5bb785f-tnjzn 1/1 Running 0 13m
工作流程:
hpa -> apiserver -> kube aggregation -> metrics-server -> kubelet(cadvisor)
3. autoscaling/v2beta2(多指标)
为满足更多的需求, HPA 还有 autoscaling/v2beta1
和 autoscaling/v2beta2
两个版本。
这两个版本的区别是 autoscaling/v1beta1支持了 Resource Metrics(CPU)和 Custom Metrics(应用程序指标),而在 autoscaling/v2beta2的版本中额外增加了 External Metrics (外部指标)的支持。
#生成yaml
kubectl get hpa.v2beta2.autoscaling -o yaml > hpa-v2.yaml
v2与上面v1版本效果一样,只不过这里格式有所变化。
v2还支持其他另种类型的度量指标:Pods和Object。
metrics中的type字段有四种类型的值:Object、Pods、Resource、External。
- Resource:指的是当前伸缩对象下的pod的cpu和memory指标,只支持Utilization(利用率)和AverageValue(平均值)类型的目标值。
- Object:指的是指定k8s内部对象的指标,数据需要第三方adapter提供,只支持Value和AverageValue类型的目标值。
- Pods:指的是伸缩对象Pods的指标,数据需要第三方的adapter提供,只允许AverageValue类型的目标值。
- External:指的是k8s外部的指标,数据同样需要第三方的adapter提供,只支持Value和AverageValue类型的目标值。
#测试的yaml文件
[root@k8s-master autoscaling]# cat hpa-v2.yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
target:
type: Value
value: 10k
工作流程:
hpa -> apiserver -> kube aggregation -> prometheus-adapter -> prometheus -> pods
4. 基于Prometheus自定义指标弹性伸缩
参考博客
https://blog.51cto.com/12480612/2457624
资源指标只包含CPU、内存,一般来说也够了。但如果想根据自定义指标:如请求qps/5xx错误数来实现HPA,就需要使用自定义指标了,目前比较成熟的实现是 Prometheus Custom Metrics。自定义指标由Prometheus来提供,再利用k8s-prometheus-adpater聚合到apiserver,实现和核心指标(metric-server)同样的效果。
1. 准备一个nfs环境
提前部署好nfs-client,否则prometheus无法启动
为防止Pod漂移到其他节点无法访问,所以需要创建一个nfs共享目录来进行挂载
#准备一台nfs服务器或者用master节点作为nfs服务器测试即可
#在服务端安装nfs软件:
yum install -y nfs-utils rpcbind
#启动rpcbind和nfs服务
systemctl restart rpcbind nfs
systemctl enable rpcbind nfs
#nfs配置文件/etc/exports
echo '/ifs/kubernetes *(rw,no_root_squash' >>/etc/exports
#创建nfs共享目录
mkdir -p /ifs/kubernetes
#平滑重启nfs
systemctl reload nfs
#检查可以挂载的目录
[root@k8s-master ~]# showmount -e
Export list for k8s-master:
/ifs/kubernetes *
#在所有k8s节点安装nfs客户端
yum install nfs-utils -y
[root@k8s-master ~]# unzip nfs-client.zip
[root@k8s-master ~]# cd nfs-client/
[root@k8s-master nfs-client]# ll
total 12
-rw-r--r-- 1 root root 225 Nov 30 2019 class.yaml
-rw-r--r-- 1 root root 994 Nov 30 2019 deployment.yaml
-rw-r--r-- 1 root root 1526 Nov 30 2019 rbac.yaml
修改deployment.yaml文件
[root@k8s-master nfs-client]# cat deployment.yaml
...
value: 192.168.0.10 #nfs服务器地址
- name: NFS_PATH
value: /ifs/kubernetes #nfs共享目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.0.10 #nfs服务器地址
path: /ifs/kubernetes #nfs共享目录
构建nfs-client
[root@k8s-master nfs-client]# kubectl apply -f .
[root@k8s-master nfs-client]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-f7886779f-cwqkr 1/1 Running 0 26s
# kubectl get pv
# kubectl get sc
查看nfs共享目录下成功了
[root@k8s-master nfs-client]# ls /ifs/kubernetes/
kube-system-grafana-data-grafana-0-pvc-469e13d0-8429-4766-b23a-af9baf3eb80c
kube-system-prometheus-data-prometheus-0-pvc-074bfaaf-4ceb-42d9-b940-d2f4905bc69b
2. 部署Prometheus
[root@k8s-master ~]# unzip prometheus.zip
[root@k8s-master ~]# cd prometheus/
[root@k8s-master prometheus]# ll
total 68
-rw-r--r-- 1 root root 5124 Nov 24 2019 prometheus-configmap.yaml
-rw-r--r-- 1 root root 1080 Nov 23 2019 prometheus-rbac.yaml
-rw-r--r-- 1 root root 4884 Nov 24 2019 prometheus-rules.yaml
-rw-r--r-- 1 root root 392 Nov 23 2019 prometheus-service.yaml
-rw-r--r-- 1 root root 3257 Jul 30 14:28 prometheus-statefulset.yaml
#当前只需要用到prometheus的yaml文件
kubectl apply -f prometheus-rbac.yaml
kubectl apply -f prometheus-configmap.yaml
kubectl apply -f prometheus-rules.yaml
kubectl apply -f prometheus-service.yaml
kubectl apply -f prometheus-statefulset.yaml
#查看Pod和svc资源并进行访问
[root@k8s-master ~]# kubectl get pod,svc -n kube-system
NAME READY STATUS RESTARTS AGE
...
pod/prometheus-0 2/2 Running 0 10m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/prometheus NodePort 10.10.82.85 <none> 9090:30090/TCP 10m
访问Prometheus UI:http://NdeIP:30090
3. 基于prometheus采集QPS速率指标测试
- 应用程序要暴露/metrics监控指标并且是prometheus数据格式
- prometheus采集入库
- 针对要监控的数据添加adapter规则(promSQL)
- adapter注册Api server
- 编写HPA规则(对应的指标名称)
部署一个应用
[root@k8s-master hpa]# cat metrics-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: metrics-app
name: metrics-app
spec:
replicas: 3
selector:
matchLabels:
app: metrics-app
template:
metadata:
labels:
app: metrics-app
annotations:
prometheus.io/scrape: "true" #表明可以采集Pod
prometheus.io/port: "80" #采集Pod允许访问的端口
prometheus.io/path: "/metrics" #采集Pod要连接的url地址
spec:
containers:
- image: 245684979/metrics-app
name: metrics-app
ports:
- name: web
containerPort: 80
resources:
requests:
cpu: 200m
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: metrics-app
labels:
app: metrics-app
spec:
ports:
- name: web
port: 80
targetPort: 80
selector:
app: metrics-app
[root@k8s-master hpa]# kubectl apply -f metrics-app.yaml
该metrics-app暴露了一个Prometheus指标接口,可以通过访问service看到:
[root@k8s-master hpa]# kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/metrics-app-b4d7dd845-5h5r9 1/1 Running 0 2m49s
pod/metrics-app-b4d7dd845-dgmlc 1/1 Running 0 2m49s
pod/metrics-app-b4d7dd845-vmdf5 1/1 Running 0 2m49
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/metrics-app ClusterIP 10.10.81.19 <none> 80/TCP 2m49s
[root@k8s-master hpa]# curl 10.10.81.19
Hello! My name is metrics-app-b4d7dd845-dgmlc. The last 10 seconds, the average QPS has been 0.5. Total requests served: 44
[root@k8s-master hpa]# curl 10.10.81.19/metrics
# HELP http_requests_total The amount of requests in total
# TYPE http_requests_total counter
http_requests_total 210 #当前Pod累计的请求值
# HELP http_requests_per_second The amount of requests per second the latest ten seconds
# TYPE http_requests_per_second gauge
http_requests_per_second 0.5 #10s之内有0.5个http请求
在prometheus的界面上可以请求到数据
创建HPA策略:
[root@k8s-master hpa]# vim app-hpa-v2.yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: metrics-app-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: metrics-app
minReplicas: 1
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: 800m # 800m 即0.8个/秒
[root@k8s-master hpa]# kubectl apply -f app-hpa-v2.yaml
[root@k8s-master hpa]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
metrics-app-hpa Deployment/metrics-app <unknown>/800m 1 10 3 15m
这里使用Prometheus提供的指标测试来测试自定义指标(QPS)的自动缩放。
获取不到值:<unknown>
但是 prometheus采集到的metrics并不能直接给k8s用,因为两者数据格式不兼容,还需要另外一个组件(k8s-prometheus-adpater),将prometheus的metrics 数据格式转换成k8s API接口能识别的格式,转换以后,因为是自定义API,所以还需要用Kubernetes aggregator在主APIServer中注册,以便直接通过/apis/来访问。
4. 部署 Custom Metrics Adapter
https://github.com/DirectXMan12/k8s-prometheus-adapter
该 PrometheusAdapter 有一个稳定的Helm Charts,可以直接使用helm的包。
准备好helm的环境:
上文已经安装好helm包管理工具了
[root@k8s-master ~]# helm repo list
NAME URL
stable http://mirror.azure.cn/kubernetes/charts
[root@k8s-master ~]# helm list
部署prometheus-adapter
指定prometheus地址:
helm install prometheus-adapter stable/prometheus-adapter --namespace kube-system --set prometheus.url=http://prometheus.kube-system,prometheus.port=9090
[root@k8s-master ~]# helm list -n kube-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
prometheus-adapter kube-system 1 2020-07-30 18:04:42.895949887 +0800 CST deployed prometheus-adapter-2.5.0 v0.7.0
[root@k8s-master ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
prometheus-adapter-56c85455f8-rrmxc 0/1 Running 0 40s
保证适配器注册到APIServer:
[root@k8s-master ~]# kubectl get apiservices |grep custom
v1beta1.custom.metrics.k8s.io kube-system/prometheus-adapter True 86s
#输出监控指标
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"
5. 配置适配器收集特定的指标
当创建好HPA还没结束,因为适配器还不知道你要什么指标(http_requests_per_second),HPA也就获取不到Pod提供指标。
ConfigMap在default名称空间中编辑prometheus-adapter ,并在该rules: 部分的顶部添加一个新的seriesQuery
kubectl edit cm prometheus-adapter -n kube-system
...
rules:
- seriesQuery: 'http_requests_total{kubernetes_namespace!="",kubernetes_pod_name!=""}'
resources:
overrides:
kubernetes_namespace: {resource: "namespace"}
kubernetes_pod_name: {resource: "pod"}
name:
matches: "^(.*)_total"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
...
该规则将http_requests在2分钟的间隔内收集该服务的所有Pod的平均速率。
需要重建prometheus-adapter的Pod才能获取到监控指标
kubectl delete pod -n kube-system prometheus-adapter-56c85455f8-zth2c
[root@k8s-master hpa]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
metrics-app-hpa Deployment/metrics-app 416m/800m 1 10 2 34m
测试一下 API:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests_per_second"
进行压力测试
ab -n 100000 -c 100 http://10.10.81.19/metrics
查看HPA状态:
[root@k8s-master ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
metrics-app-hpa Deployment/metrics-app 254176m/800m 1 10 4 6m29s
[root@k8s-master ~]# kubectl describe hpa metrics-app-hpa
Normal SuccessfulRescale 53s horizontal-pod-autoscaler New size: 4; reason: pods metric http_requests_per_second above target
Normal SuccessfulRescale 37s horizontal-pod-autoscaler New size: 8; reason: pods metric http_requests_per_second above target
Normal SuccessfulRescale 22s horizontal-pod-autoscaler New size: 10; reason: pods metric http_requests_per_second above target
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
metrics-app-b4d7dd845-2mr5c 0/1 ContainerCreating 0 50s
metrics-app-b4d7dd845-bndtx 0/1 Running 0 50s
metrics-app-b4d7dd845-dgmlc 1/1 Running 2 16h
metrics-app-b4d7dd845-dlbjn 1/1 Running 0 80s
metrics-app-b4d7dd845-fvmlp 0/1 Running 0 65s
metrics-app-b4d7dd845-ldqwg 1/1 Running 0 65s
metrics-app-b4d7dd845-npkzg 1/1 Running 0 65s
metrics-app-b4d7dd845-vmdf5 1/1 Running 1 16h
metrics-app-b4d7dd845-zpvdr 1/1 Running 0 80s
metrics-app-b4d7dd845-zx66k 1/1 Running 0 65s
6. 总结
图片来自网络- 通过/metrics收集每个Pod的http_request_total指标;
- prometheus将收集到的信息汇总;
- APIServer定时从Prometheus查询,获取request_per_second的数据;
- HPA定期向APIServer查询以判断是否符合配置的autoscaler规则;
- 如果符合autoscaler规则,则修改Deployment的ReplicaSet副本数量进行伸缩。
- 监控的是所有Pod的平均值,是一个deployment中部署的。
- 暴露的/metrics接口需要开发在代码框架中提前定义好用prometheus采集相关的
Url
接口。也有一些应用默认自带prometheus的监控接口。可以去查找prometheus针对于应用程序暴露指标的客户端。