③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-dnskube-proxy也都是使用的Deployment来管理的,所以最好使用Deployment来管理Pod

image

可以看出一个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字段解析

image

4. 使用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,直到全部替换更新

进行访问会发现无法访问

image

需要修改service的端口为nginx的80

 [root@k8s-master ~]# kubectl edit service web
 ...
    targetPort: 80

再次访问已经为nginx的首页了

image


查看升级状态

[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的生命是有限的,死亡过后不会复活了。RCDeployment可以用来动态的创建和销毁Pod。尽管每个Pod都有自己的IP地址,但是如果Pod重新启动了的话那么它的IP很有可能也就变化了。这就会带来一个问题:比如我们有一些后端的Pod的集合为集群中的其他前端的Pod集合提供API服务,如果我们在前端的Pod中把所有的这些后端的Pod的地址都写死了,然后去某种方式去访问其中一个Pod的服务,这样看上去是可以正常工作的,但是如果这个Pod挂掉了,然后重新启动起来了,IP地址非常有可能就变了,这个时候前端就极大可能访问不到后端的服务了。

遇到这样的问题该怎么解决呢?:question:

  • 比如我们在部署一个WEB服务的时候,前端一般部署一个Nginx作为服务的入口,然后Nginx后面肯定就是挂载的其他后端的各种服务,以前我们可能是去手动更改Nginx配置中的资源池upstream选项,来动态改变提供服务的数量

  • 如果要解决上面遇到的问题,可以通过服务发现的工具解决掉。当Pod被销毁或者新建过后,我们可以把这个Pod的地址注册到这个服务发现中心去就可以。但是这样的话我们的前端的Pod结合就不能直接去连接后台的Pod集合了,所以还应该连接到一个能够做服务发现的中间件上面。

  • Kubernetes集群就为我们提供了这样的一个对象 - ServiceService是一种抽象的对象,它定义了一组Pod的逻辑集合和一个用于访问它们的策略,这个概念和微服务非常类似。一个Serivce下面包含的Pod集合一般是由Label Selector(标签)来决定的。

  • 上面举的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的Pod集合会发送变化,但是前端的Pod却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service的这种抽象对象就可以帮我们达到这种解耦的目的。

1. Kubernetes的三种IP

  • Node IP:Node节点的IP地址
  • Pod IP: Pod的IP地址
  • Cluster IP: ServiceIP地址

:one:首先,Node IPKubernetes集群中节点的物理网卡doIP地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以Kubernetes集群外要想访问Kubernetes集群内部的某个节点或者服务,肯定得通过Node IP进行通信(这个时候一般是通过外网IP了)

:two:然后Pod IP是每个PodIP地址,它是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-demoService对象,具有标签app=java-demoPod上,这个Service会被系统分配一个Cluster IP,该Service还会持续的监听selector下面的Pod,会把这些Pod信息更新到一个名为java-demoendpoints对象上去,这个对象就类似于我们上面说的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:

  • 使用 NodePortLoadBlancer 类型的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 有很多,比如 traefiknginx-controllerKubernetes Ingress Controller for KongHAProxy Ingress controller,当然你也可以自己实现一个 Ingress Controller,现在普遍用得较多的是 traefik 和 nginx-controller,traefik 的性能较 nginx-controller 差,但是配置使用要简单许多。

2. 各种 Ingress 横向对比

image

2. Pod与Ingress的关系

  • 通过Service关联Pod
  • 基于域名访问
  • 通过Ingress Controller实现Pod的负载均衡
    • 支持TCP/UDP 4层和HTTP 7层

3. Ingress Nginx的工作原理分析

参考博客链接

Nginx-ingressKubernetes生态中的重要成员,主要负责向外暴露服务,同时提供负载均衡等附加功能;

nginx-ingress已经能够完成 7/4 层的代理功能;4层代理基于 ConfigMap

1. Nginx 的 7 层反向代理模式

image

Nginx 对后端运行的服务(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 工作流程分析

image

nginx-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解析

image

在本地进行ping是否畅通

image

浏览器使用域名访问

image

如果想不使用端口访问需要在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

浏览器访问并刷新页面查看负载均衡的效果

image
image

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

支持的云提供商:

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/v1autoscaling/v2beta1autoscaling/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/v2beta1autoscaling/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

nfs-client文件包下载链接:提取码: rr89

[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

Prometheus文件包下载链接: 提取码: 3buh

[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. 总结

图片来自网络
  1. 通过/metrics收集每个Pod的http_request_total指标;
  2. prometheus将收集到的信息汇总;
  3. APIServer定时从Prometheus查询,获取request_per_second的数据;
  4. HPA定期向APIServer查询以判断是否符合配置的autoscaler规则;
  5. 如果符合autoscaler规则,则修改Deployment的ReplicaSet副本数量进行伸缩。
  6. 监控的是所有Pod的平均值,是一个deployment中部署的。
  7. 暴露的/metrics接口需要开发在代码框架中提前定义好用prometheus采集相关的Url接口。也有一些应用默认自带prometheus的监控接口。可以去查找prometheus针对于应用程序暴露指标的客户端。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342