aspnetcore.webapi实践k8s健康探测机制 – kubernetes

1、浅析k8s两种健康检查机制

  • Liveness
    k8s通过liveness来探测微服务的存活性,判断什么时候该重启容器实现自愈。比如访问 Web 服务器时显示 500 内部错误,可能是系统超载,也可能是资源死锁,此时 httpd 进程并没有异常退出,在这种情况下重启容器可能是最直接最有效的解决方案。

  • Readiness
    k8s通过readiness来探测微服务的什么时候准备就绪(例如初始化时,连接数据库,加载缓存数据等等,可能需要一段时间),然后将容器加入到server的负载均衡池中,对外提供服务。

1.1、k8s默认的健康检查机制

  每个容器启动时都会执行一个进程,此进程由 Dockerfile 的 CMD 或 ENTRYPOINT 指定。如果进程退出时返回码非零,则认为容器发生故障,Kubernetes 就会根据 restartPolicy 重启容器。如果不特意配置,Kubernetes 将对两种探测采取相同的默认行为。

2、通过微服务自定义两种机制

存活10分钟:如果当前时间超过服务启动时间10分钟,则探测失败,否则探测成功。Kubernetes 如果连续执行 3 次 Liveness 探测均失败,就会杀掉并重启容器。

未分类

准备就绪30秒,30秒后,如果连续 3 次 Readiness 探测均失败后,容器将被重置为不可用,不接收 Service 转发的请求。

未分类

从上面可以看到,我们可以根据自身的需求来实现这两种机制,然后,提供给k8s进行探测。

3、编写k8s资源配置文件(yml)

k8s默认是根据命令进行探测的,由于我们需要与微服务结合,所以需要在yml文件中指定为http方式,k8s对于http方式探测成功的判断条件是请求的返回代码在 200-400 之间。

health-checks-deployment.yml 如下:

未分类

从上面可以看到,一共部署了3个pod副本,而每个pod副本里面部署一个容器,即为同一个微服务部署了3个实例进行集群。

4、在k8s集群的master机器上,创建部署对象

未分类

从上面可以看到,刚开始创建时,READY 状态为不可用,等待一段时间

未分类

现在全部可用了

5、通过dashboard查看集群概况

未分类

6、剖析k8s集群自愈(self-healing)过程

未分类

未分类

未分类

从上面可以看到,大约1分钟(dashboard统计信息有一定的延迟)左右,第一次进行 Readiness 探测并成功返回,此时准备就绪,可以对外提供服务了。在10分钟内,探测Liveness也成功返回。

继续等待一段时间,查询其中一个pod详细信息:

未分类

未分类

从上面可以看到,超过10分钟存活期后,liveness探测失败,容器被 killed and recreated。探测Readiness未成功返回时,整个容器处于不健康的状态,并不会被负载均衡请求。

此时通过dashboard查看集群概况:

未分类

继续等待一段时间:

未分类

现在,整个集群已经自愈完成了!!!

7、总结

Liveness 探测和 Readiness 探测是独立执行的,二者之间没有依赖,可以单独使用,也可以同时使用。用 Liveness 探测判断容器是否需要重启以实现自愈;用 Readiness 探测判断容器是否已经准备好对外提供服务。

源码参考:https://github.com/justmine66/k8s.ecoysystem.apps

k8s与监控–引入traefik做后端服务的反代

前言

对于监控这块,我们基于prometheus实现,当然做了大量的优化,包括前面所讲到的配置接口化。我们整个监控的UI部分,没有采用社区流行的grafana,而是自己实现了一套。我们后端的服务按照功能拆分了几大块,例如拓扑,网络流量,配置,元数据等等。拆分的好处就是可以解耦,各个模块功能的升级不影响其他模块。但是对于前端来说,只暴露一个入口,引入一个反代即可。
刚开始选用了nginx,后期由于要加入鉴权的功能,nginx就不能满足我们的需求了。这个时候基本上需求就变为选择一个可编程的反代。当然我在做电商的时候,我们经常采用openresty,结合nginx和lua,可以实现。而且社区基于openresty实现了kong和orange等api网关。
但是考虑我们的场景,整个项目并没有特别高的并发和性能要求,而且我们团队最熟悉的是golang和python。所以选择了traefik。我们可以在后期,可以写各种的插件来满足我们的需求。

traefik简介

Træfɪk 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。 它支持多种后台 (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, file…) 来自动化、动态的应用它的配置文件设置。

未分类

简单总结一下我认为traefik的特点:

  1. 官方测试traefik有nginx 85%的性能。这个性能对于一般项目足够了,换来的是强大的编程能力,来从容应对各种需求。而且golang编写,无依赖。
  2. 配置热更新,支持多种后端。
  3. 支持集群模式
  4. 提供了一个web UI

结合项目写demo

我们的项目目前基本两个需求,鉴权和反代。

配置文件

对于反代。主要讲一些配置相关。我们采用的是file。traefik对这种静态文件支持watcher。依旧无需重启进程。

主配置文件traefik.toml

################################################################
# Global configuration
################################################################

# Enable debug mode
#
# Optional
# Default: false
#
# debug = true

# Log level
#
# Optional
# Default: "ERROR"
#
# logLevel = "ERROR"

# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
# Optional
# Default: ["http"]
#
# defaultEntryPoints = ["http", "https"]

# Entrypoints definition
#
# Optional
# Default:
[entryPoints]
    [entryPoints.http]
    address = ":8000"

# Traefik logs
# Enabled by default and log to stdout
#
# Optional
#
# [traefikLog]

# Sets the filepath for the traefik log. If not specified, stdout will be used.
# Intermediate directories are created if necessary.
#
# Optional
# Default: os.Stdout
#
# filePath = "log/traefik.log"

# Format is either "json" or "common".
#
# Optional
# Default: "common"
#
# format = "common"

# Enable access logs
# By default it will write to stdout and produce logs in the textual
# Common Log Format (CLF), extended with additional fields.
#
# Optional
#
# [accessLog]

# Sets the file path for the access log. If not specified, stdout will be used.
# Intermediate directories are created if necessary.
#
# Optional
# Default: os.Stdout
#
# filePath = "/path/to/log/log.txt"

# Format is either "json" or "common".
#
# Optional
# Default: "common"
#
# format = "common"

################################################################
# Web configuration backend
################################################################

# Enable web configuration backend
[web]

# Web administration port
#
# Required
#
address = ":8080"

################################################################
# Docker configuration backend
################################################################

# Enable Docker configuration backend
# [docker]

# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
# Default: "unix:///var/run/docker.sock"
#
# endpoint = "tcp://10.10.10.10:2375"

# Default domain used.
# Can be overridden by setting the "traefik.domain" label on a container.
#
# Optional
# Default: ""
#
# domain = "docker.localhost"

# Expose containers by default in traefik
#
# Optional
# Default: true
#
# exposedbydefault = true


################################################################
# File configuration backend
################################################################
[file]
  filename = "rules.toml"
  watch = true

注意filename = “rules.toml”,这个时候我把所有的代理规则写到一个rules.toml文件中,也算一种解耦的思路。
当然traefik也支持多文件。就是你可以指定一个路径,然后会将该路径下所有rule文件加载

[file]
  directory = "/path/to/config/"

下面是demo中的rules.toml

[backends]
  [backends.trend]
    [backends.trend.servers]
      [backends.trend.servers.server1]
      url = "http://api.domain.com:8812"
      weight = 1
    [backends.trend.healthcheck]
      path = "/"
      interval = "10s"
# Frontends
[frontends]
  [frontends.trend]
  backend = "trend"
   [frontends.trend.routes.router1]
    rule = "PathPrefixStrip:/trend"

启动traefik

执行

./traefik --c traefik.toml

实际效果

访问ui:

未分类

对于鉴权:

traefik在中间件中支持了几种auth

  1. basic auth
  2. forward

目前forward基本能满足我们的需求。将请求转发到统一认证服务。
当然oauth,jwt等之类是目前不支持的,但是实现起来很简单,增加一个中间件而已。

总结

没有最好的技术,只有合适的场景。

Kubernetes之利用prometheus监控K8S集群

prometheus它是一个主动拉取的数据库,在K8S中应该展示图形的grafana数据实例化要保存下来,使用分布式文件系统加动态PV,但是在本测试环境中使用本地磁盘,安装采集数据的agent使用DaemonSet来部署,DaemonSet的特性就是在每个node上部署一个服务进程,这一切都是自动的部署。

此处只讲如何用prometheus来监控K8S集群,关于prometheus的知识参考官方文档。

部署前提: 准备好所需要的文件

$ ls -l 
Prometheus/prometheus#:/data/Prometheus/prometheus# ls -l 
total 28
drwxr-xr-x 2 root root 4096 Jan 15 02:53 grafana
drwxr-xr-x 2 root root 4096 Jan 15 03:11 kube-state-metrics
-rw-r--r-- 1 root root   60 Jan 14 06:48 namespace.yaml
drwxr-xr-x 2 root root 4096 Jan 15 03:22 node-directory-size-metrics
drwxr-xr-x 2 root root 4096 Jan 15 03:02 node-exporter
drwxr-xr-x 2 root root 4096 Jan 15 02:55 prometheus
drwxr-xr-x 2 root root 4096 Jan 15 02:37 rbac

$ ls grafana/
grafana-configmap.yaml  grafana-core-deployment.yaml  grafana-import-dashboards-job.yaml  grafana-pvc-claim.yaml  grafana-pvc-volume.yaml  grafana-service.yaml

$ ls prometheus/
configmap.yaml  deployment.yaml  prometheus-rules.yaml  service.yaml

grafana和 prometheus 都是部署文件,node-exporter、kube-state-metrics、node-directory-size-metrics这三个是采集器,相当于prometheus的agent

文件准备好了,现在开始一步一步来部署:

1、创建所需Namespace

因为prometheus 部署的所有的deploy、pod、svc都是在monitoring完成的,所以需要事先创建之。

 $ cat namespace.yaml 
 apiVersion: v1
 kind: Namespace
 metadata:
  name: monitoring

 $ kubectl create -f namespace.yaml 
 namespace "monitoring" created

2、创建grafana的pv、 pvc

grafana# cat grafana-pvc-volume.yaml 
kind: PersistentVolume
apiVersion: v1
metadata:
  name: grafana-pv-volume
  labels:
    type: local
spec:
  storageClassName: grafana-pv-volume
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  hostPath:
    path: "/data/volume/grafana"

grafana# cat grafana-pvc-claim.yaml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: grafana-pvc-volume
  namespace: "monitoring"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: grafana-pv-volume

$ kubectl create -f grafana/grafana-pvc-volume.yaml -f grafana/grafana-pvc-claim.yaml 
persistentvolume "grafana-pv-volume" created
persistentvolumeclaim "grafana-pvc-volume" created

$ kubectl get pvc -n monitoring
NAME          STATUS           VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS     AGE
grafana-pvc-volume   Bound     grafana-pv-volume   10Gi       RWO     grafana-pv-volume   52s

状态bound已绑定到了 grafana-pv-volume

3、创建grafana应用,这些应用都是第三方的,都会有自已的配置,通过configmap来定义

grafana# ls
grafana-configmap.yaml  grafana-core-deployment.yaml  grafana-import-dashboards-job.yaml  grafana-pvc-claim.yaml  grafana-pvc-volume.yaml  grafana-service.yaml
grafana# kubectl create -f ./    #grafana目录下所有文件都创建
configmap "grafana-import-dashboards" created
deployment "grafana-core" created
job "grafana-import-dashboards" created
service "grafana" created 


grafana# kubectl get deployment,pod -n monitoring 
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/grafana-core   1         1         1            0           1m

NAME                              READY     STATUS              RESTARTS   AGE
po/grafana-core-9c7f66868-7q8lx   0/1       ContainerCreating   0          1m
运行po/grafana-core 容器时会下载镜像: grafana/grafana:4.2.0

grafana创建的应用 简单的自已描述了下:

      grafana-pv-volume=/data/volume/grafana =10G    
      grafana-pvc-volume=5G--->grafana-pv-volume
      ---configmap=grafana-import-dashboards     
      Job=grafana-import-dashboards

      Deployment=grafana-core     replicas: 1  containers=grafana-core   mount:  grafana-pvc-volume:/var
      service=grafana     port: 3000  = nodePort: 30161     (3000是grafana服务的默认端口)

4、现在grafana的核心应用已部署好了,现在来部署prometheus的RBAC

prometheus/rbac# ls
grant_serviceAccount.sh  prometheus_rbac.yaml
#先创建RBAC文件:
prometheus/rbac# kubectl create -f prometheus_rbac.yaml 
clusterrolebinding "prometheus-k8s" created
clusterrolebinding "kube-state-metrics" created
clusterrole "kube-state-metrics" created
serviceaccount "kube-state-metrics" created
clusterrolebinding "prometheus" created
clusterrole "prometheus" created
serviceaccount "prometheus-k8s" created
prometheus/rbac#

5、创建prometheus的deloyment,service

prometheus/prometheus# ls
configmap.yaml  deployment.yaml  prometheus-rules.yaml  service.yaml
prometheus/prometheus# 
在configmap.yaml中要注意的是在1.7以后,获取cadvsion监控pod等的信息时,用的是kubelet的4194端口,
注意以下这段:这是采集cadvision信息,必须是通过kubelet的4194端口,所以Kubelet必须监听着,4194部署了cadvsion来获取pod中容器信息
prometheus/prometheus#cat configmap.yaml
 # https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml#L37
      - job_name: 'kubernetes-nodes'
        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
        kubernetes_sd_configs:
          - role: node
        relabel_configs:
          - source_labels: [__address__]
            regex: '(.*):10250'
            replacement: '${1}:10255'
            target_label: __address__
      - job_name: 'kubernetes-cadvisor'
        scheme: https
        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
        kubernetes_sd_configs:
          - role: node
        relabel_configs:
        - action: labelmap
          regex: __meta_kubernetes_node_label_(.+)
        - target_label: __address__
          replacement: kubernetes.default.svc.cluster.local:443
        - source_labels: [__meta_kubernetes_node_name]
          regex: (.+)
          target_label: __metrics_path__
          replacement: /api/v1/nodes/${1}:4194/proxy/metrics

      # https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml#L79

prometheus-rules.yaml 这是它的发现规则文件

deployment.yaml service.yaml 这两个是部署的文件, deployment部署中资源限制建议放大一点

现在部署prometheus目录下所有文件:

prometheus/prometheus# kubectl create -f ./
configmap "prometheus-core" created
deployment "prometheus-core" created
configmap "prometheus-rules" created
service "prometheus" created
prometheus/prometheus# 

prometheus/prometheus# kubectl get deployment,pod -n monitoring 
NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/grafana-core      1         1         1            1           16m
deploy/prometheus-core   1         1         1            1           1m

NAME                                  READY     STATUS    RESTARTS   AGE
po/grafana-core-9c7f66868-wm68j       1/1       Running   0          16m
po/prometheus-core-6dc6777c5b-5nc7j   1/1       Running   0          1m
prometheus应用的部署,简单描述下创建的内容:
1
2
    Deployment= prometheus-core   replicas: 1    containers=prometheus   image: prom/prometheus:v1.7.0    containerPort: 9090(webui)
    Service    name: prometheus   NodePort-->port: 9090 -webui

6、prometheus部署完了现在来部署它的agent,也就是采集器:

Prometheus/prometheus# ls node-directory-size-metrics/
daemonset.yaml
Prometheus/prometheus# ls kube-state-metrics/
deployment.yaml  service.yaml
Prometheus/prometheus# ls node-exporter/
exporter-daemonset.yaml  exporter-service.yaml
Prometheus/prometheus# 
#其中两个用的是daemonset

Prometheus/prometheus# kubectl create -f node-exporter/ -f kube-state-metrics/ -f node-directory-size-metrics/
daemonset "prometheus-node-exporter" created
service "prometheus-node-exporter" created
deployment "kube-state-metrics" created
service "kube-state-metrics" created
daemonset "node-directory-size-metrics" created
Prometheus/prometheus# 

Prometheus/prometheus# kubectl get deploy,pod,svc -n monitoring 
NAME                        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/grafana-core         1         1         1            1           26m
deploy/kube-state-metrics   2         2         2            2           1m
deploy/prometheus-core      1         1         1            1           11m

NAME                                     READY     STATUS    RESTARTS   AGE
po/grafana-core-9c7f66868-wm68j          1/1       Running   0          26m
po/kube-state-metrics-694fdcf55f-bqcp8   1/1       Running   0          1m
po/kube-state-metrics-694fdcf55f-nnqqd   1/1       Running   0          1m
po/node-directory-size-metrics-n9wx7     2/2       Running   0          1m
po/node-directory-size-metrics-ppscw     2/2       Running   0          1m
po/prometheus-core-6dc6777c5b-5nc7j      1/1       Running   0          11m
po/prometheus-node-exporter-kchmb        1/1       Running   0          1m
po/prometheus-node-exporter-lks5m        1/1       Running   0          1m

NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
svc/grafana                    NodePort    10.254.231.25   <none>        3000:30161/TCP   26m
svc/kube-state-metrics         ClusterIP   10.254.156.51   <none>        8080/TCP         1m
svc/prometheus                 NodePort    10.254.239.90   <none>        9090:37318/TCP   10m
svc/prometheus-node-exporter   ClusterIP   None            <none>        9100/TCP         1m
Prometheus/prometheus#

--------
Prometheus/prometheus# kubectl get pod -o wide -n monitoring 
NAME                                  READY     STATUS    RESTARTS   AGE       IP             NODE
prometheus-node-exporter-kchmb        1/1       Running   0          4m        10.3.1.16      10.3.1.16
prometheus-node-exporter-lks5m        1/1       Running   0          4m        10.3.1.17      10.3.1.17

#这两个是exporter,用的是daemonset 分别在这两个node上运行了。这样就可以采集到所有数据了。

如上部署完成,以下是用自已的话简单描述下:

 node-exporter/exporter-daemonset.yaml 文件:
       DaemonSet=prometheus-node-exporter   
          containers: name: prometheus-node-exporter    image: prom/node-exporter:v0.14.0
          containerPort: 9100   hostPort: 9100  hostNetwork: true    #它用的是主机的9100端口

        Prometheus/prometheus/node-exporter# kubectl get  daemonset,pod -n monitoring 
        NAME                             DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
        ds/node-directory-size-metrics   2         2         2         2            2           <none>          16h
        ds/prometheus-node-exporter      2         2         2         2            2           <none>          16h
           因为它是daemonset,所以相应的也会运行着两个Pod: prometheus-node-exporter

      Service=prometheus-node-exporter   clusterIP: None   port: 9100  type: ClusterIP   #它没有clusterIP

    # kubectl get  service -n monitoring 
    NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
    prometheus-node-exporter   ClusterIP   None            <none>        9100/TCP         16h
kube-state-metrics/deployment.yaml 文件:
      Deployment=kube-state-metrics replicas: 2   containers-->name: kube-state-metrics  image: gcr.io/google_containers/kube-state-metrics:v0.5.0 
                 containerPort: 8080

      Service     name: kube-state-metrics   port: 8080  #没有映射
                                 #kubectl get deployment,pod,svc -n monitoring                               
            NAME                        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
            deploy/kube-state-metrics   2         2         2            2           16h

            NAME                                     READY     STATUS    RESTARTS   AGE
            po/kube-state-metrics-694fdcf55f-2mmd5   1/1       Running   0          11h
            po/kube-state-metrics-694fdcf55f-bqcp8   1/1       Running   0          16h

            NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
            svc/kube-state-metrics         ClusterIP   10.254.156.51   <none>        8080/TCP         16h
node-directory-size-metrics/daemonset.yaml 文件:
        #因为是daemonset,所以未定义replicas数量,直接运行在每个node之上,但是它没有创建service
      DaemonSet : name: node-directory-size-metrics  
                  containers-->name: read-du  image: giantswarm/tiny-tools   mountPath: /mnt/var   mountPath: /tmp
                  containers--> name: caddy    image: dockermuenster/caddy:0.9.3 containerPort: 9102
                               mountPath: /var/www   hostPath /var

        kubectl get daemonset,pod,svc -n monitoring 
        NAME                             DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
        ds/node-directory-size-metrics   2         2         2         2            2           <none>          16h


        NAME                                     READY     STATUS    RESTARTS   AGE
        po/node-directory-size-metrics-n9wx7     2/2       Running   0          16h
        po/node-directory-size-metrics-ppscw     2/2       Running   0          16h

        NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
                     没有node-directory-size-metrics的service

到此 prometheus算是部署完成了,最后来看下它暴露的端口:

Prometheus/prometheus# kubectl get svc -o wide -n monitoring 
NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE       SELECTOR
grafana                    NodePort    10.254.231.25   <none>        3000:30161/TCP   31m       app=grafana,component=core
kube-state-metrics         ClusterIP   10.254.156.51   <none>        8080/TCP         6m        app=kube-state-metrics
prometheus                 NodePort    10.254.239.90   <none>        9090:37318/TCP   16m       app=prometheus,component=core
prometheus-node-exporter   ClusterIP   None            <none>        9100/TCP         6m        app=prometheus,component=node-exporter
Prometheus/prometheus#

7、访问、使用prometheus

如上可以看到grafana的端口号是30161,NodeIP:30161 就可以打开grafana,默认admin/admin

未分类

登录后,添加数据源:

未分类

添加Prometheus的数据源:

将Prometheus的作为数据源的相关参数如下图所示:

未分类

添加完后,导入模板文件:

未分类

未分类

未分类

部署完成。

k8s实战-创建ConfigMap

k8s的ConfigMap用来保存配置数据,以键值对形式存储,既可以保存单个属性,也可以保存配置文件。使用ConfigMao前请确保已经安装好了k8s集群,在master主机上执行kubectl create configmap –help,可以看到该命令的使用方法kubectl create configmap map-name map-source。

kubectl create configmap my-config –from-file=path/to/dir

  
该命令以文件目录为源创建ConfigMap,key为文件名,value为文件内容,子文件夹及其下文件将被忽略,例如,k8s-cfg文件夹下有4个文件,文件结构及内容为:

[root@niuhp-vm tmp]# cat k8s-cfg/dir1/file4.data
i am in dir1
[root@niuhp-vm tmp]# cat k8s-cfg/file1
abcdefg
[root@niuhp-vm tmp]# cat k8s-cfg/file2.text
1234567
[root@niuhp-vm tmp]# cat k8s-cfg/file3.log
k1=adsdf,k2=23424,k3=35434

在控制台执行kubectl create configmap my-config-from-dir –from-file=k8s-cfg,成功的话我们会看到如下提示:

[root@niuhp-vm tmp]# create configmap my-config-from-dir --from-file=k8s-cfg
configmap "my-config-from-dir" created

从控制台看下这个ConfigMap的内容

[root@niuhp-vm tmp]# kubectl describe configmap my-config-from-dir
Name:           my-config-from-dir
Namespace:      default
Labels:         <none>
Annotations:    <none>
Data
====
file1:
----
abcdefg
file2.text:
----
1234567
file3.log:
----
k1=adsdf,k2=23424,k3=35434
Events: <none>

从dashboad看下

未分类

另外可以通过参数–namespace={namespace-name}指定命令空间。

kubectl create configmap my-config –from-file=[key1=]/path/to/file1.txt –from-file=[key2=]/path/to/file2.txt

  
该命令以多个文件为源创建ConfigMap,key为文件名(也可以指定),value为文件内容,例如执行create configmap my-config-from-files –from-file=k8s-cfg/file1 –from-file=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

执行kubectl create configmap my-config-from-files-custom-key –from-file=mykey1=k8s-cfg/file1 –from-file=mykey2=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

kubectl create configmap my-config –from-literal=key1=config1 –from-literal=key2=config2

  
该命令以输入的多个键值对为源创建ConfigMap,例如执行kubectl create configmap my-config-from-kv –from-literal=mykeyzh=nihao –from-literal=mykeyen=hello –from-literal=mykeynum=12345创建的ConfigMap为

未分类

kubectl create configmap my-config –from-env-file=path/to/file

  
该命令以存放键值对的文件为源创建ConfigMap,例如:文件 k8s-test.properties中文件内容如下

[root@niuhp-vm tmp]# cat k8s-test.properties
a=1
b=2
c=3
key2=2sfsdf

执行kubectl create configmap my-config-from-envfile –from-env-file=k8s-test.properties创建的ConfigMap为

未分类

k8s基于hpa实现pod弹性扩容

要使用hpa,第一步是安装heapster(下面的10.135.19.77换成自己k8s可以访问到的ip)

wget https://github.com/kubernetes/heapster/archive/master.zip  
unzip master.zip  
cd heapster-master/

sed -i "s/gcr.io/google_containers/heapster-grafana-amd64:v4.4.3/index.tenxcloud.com/jimmy/heapster-grafana-amd64:v4.0.2/g" deploy/kube-config/influxdb/grafana.yaml

sed -i "s/gcr.io/google_containers/heapster-amd64:v1.4.0/index.tenxcloud.com/jimmy/heapster-amd64:v1.3.0-beta.1/g" deploy/kube-config/influxdb/heapster.yaml

sed -i "s/https://kubernetes.default/http://10.135.19.77:8080?inClusterConfig=false&useServiceAccount=false/g" deploy/kube-config/influxdb/heapster.yaml

sed -i "s/monitoring-influxdb.kube-system.svc/10.135.19.77/g" deploy/kube-config/influxdb/heapster.yaml

sed -i "s/gcr.io/google_containers/heapster-influxdb-amd64:v1.3.3/index.tenxcloud.com/jimmy/heapster-influxdb-amd64:v1.1.1/g" deploy/kube-config/influxdb/influxdb.yaml


/bin/bash deploy/kube.sh start

编写测试例子

  • 编写文件a.yaml
apiVersion: extensions/v1beta1  
kind: Deployment  
metadata:  
  name: my-app
spec:  
  replicas: 2
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: registry.alauda.cn/yubang/paas_base_test
        ports:
        - containerPort: 80
        command: ["/bin/bash", "/var/start.sh"] 
        resources:  
          limits:  
            cpu: 0.01  
            memory: 64Mi
  • 编写文件b.yaml
apiVersion: v1  
kind: Service  
metadata:  
  name: my-app-svc
  labels:
    app: my-app
spec:  
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30964
  type: NodePort
  selector:
    app: my-app
  • 编写文件c.yaml
apiVersion: autoscaling/v1  
kind: HorizontalPodAutoscaler  
metadata:  
  name: my-app-hpa
  namespace: default
spec:  
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: my-app
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 5

启动服务

kubectl apply -f a.yaml --validate  
kubectl apply -f b.yaml --validate  
kubectl apply -f c.yaml --validate  

查看是否正常运行

kubectl get horizontalpodautoscaler

让外部网络访问K8S service的四种方式

本文基于kubernetes 1.5.2版本编写

kube-proxy+ClusterIP

kubernetes版本大于或者等于1.2时,配置:

修改master的/etc/kubernetes/proxy,把KUBE_PROXY_ARGS=”“改为KUBE_PROXY_ARGS=”–proxy-mode=userspace”
重启kube-proxy服务
在核心路由设备或者源主机上添加一条路由,访问cluster IP段的路由指向到master上。

kubernetes版本小于1.2时,直接添加路由

未分类

负载均衡器+NodePort

部署一个负载均衡器(nginx、keepalive等)

未分类

Ingress

Ingress是一种HTTP方式的路由转发机制,由Ingress Controller和HTTP代理服务器组合而成。Ingress Controller实时监控Kubernetes API,实时更新HTTP代理服务器的转发规则。HTTP代理服务器有GCE Load-Balancer、HaProxy、Nginx等开源方案。 详细说明请见http://blog.csdn.net/liyingke112/article/details/77066814

未分类

loadbalance

LoadBalancer在NodePort基础上,K8S可以请求底层云平台创建一个负载均衡器,将每个Node作为后端,进行服务分发。该模式需要底层云平台(例如GCE)支持。

K8S APISERVER源码: 服务启动

基于版本 1.6.7

启动流程

未分类

  • cmd/kube-apiserver/apiserver.go
func main() {
   app.Run(s)
}
  • cmd/kube-apiserver/app/server.go
func Run(s *options.ServerRunOptions) error {
    // 构建master配置信息
    config, sharedInformers, err := BuildMasterConfig(s)
    // 调用RunServer
    return RunServer(config, sharedInformers, wait.NeverStop)
}

func RunServer(config *master.Config, sharedInformers informers.SharedInformerFactory, stopCh <-chan struct{}) error {
    // 执行相关初始化
    m, err := config.Complete().New()     // => TO: Container初始化
    // 启动
    return m.GenericAPIServer.PrepareRun().Run(stopCh)  // => next
}
  • vendor/k8s.io/apiserver/pkg/server/genericapiserver.go

启动主体函数都在这个文件中, 绑定地址/端口号, 并最终启动

func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
    s.NonBlockingRun(stopCh)
}

func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) error {
    s.serveSecurely(internalStopCh)
    // or
    s.serveInsecurely(internalStopCh) // => next
}
  • vendor/k8s.io/apiserver/pkg/server/serve.go
func (s *GenericAPIServer) serveInsecurely(stopCh <-chan struct{}) error {
    insecureServer := &http.Server{
        Addr:           s.InsecureServingInfo.BindAddress,
        Handler:        s.InsecureHandler,   // s.Hnalder for secure
        MaxHeaderBytes: 1 << 20,
    }
   runServer(insecureServer, s.InsecureServingInfo.BindNetwork, stopCh) // => next
}


func runServer(server *http.Server, network string, stopCh <-chan struct{}) (int, error) {
    go func() {
        for {
            var listener net.Listener
            listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
            // *http.Server
            err := server.Serve(listener)
            }
    }()
}

Container初始化

  • cmd/kube-apiserver/app/server.go
func RunServer(config *master.Config, sharedInformers informers.SharedInformerFactory, stopCh <-chan struct{}) error {
    // 执行相关初始化
    m, err := config.Complete().New()     // => TO: Container初始化
    // 启动
    return m.GenericAPIServer.PrepareRun().Run(stopCh)  // => next
}
  • kubernetes/pkg/master/master.go
func (c completedConfig) New() (*Master, error) {
   // m.GenericAPIServer.HandlerContainer = APIContainer,   APIContainer.Container =  restful.NewContainer()
    s, err := c.Config.GenericConfig.SkipComplete().New() // completion is done in Complete, no need for a second time
   m := &Master{
        GenericAPIServer: s,
    }
}
  • vendor/k8s.io/apiserver/pkg/server/config.go

到这里, 完成了 s.Handler, s.InsecureHandler 的初始化

func (c completedConfig) New() (*GenericAPIServer, error) {
  s := &GenericAPIServer{
  }
  // s.HandlerContainer = APIContainer
    s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)  // => next 1

  // 生成 Handler
    s.Handler, s.InsecureHandler = c.BuildHandlerChainsFunc(s.HandlerContainer.ServeMux, c.Config)  // => next 2
}
  • 1: vendor/k8s.io/apiserver/pkg/server/mux/container.go

新建一个APIContainer, 包含

// NewAPIContainer constructs a new container for APIs
func NewAPIContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *APIContainer {
    c := APIContainer{
        // 新建一个Container
        Container: restful.NewContainer(),
        NonSwaggerRoutes: PathRecorderMux{
            mux: mux,
        },
        UnlistedRoutes: mux,
    }
    // 配置 http.ServerMux
    c.Container.ServeMux = mux
    // 配置路由方式, 使用CurlyRouter
    c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
    return &c
}
  • 2: vendor/k8s.io/apiserver/pkg/server/config.go
type Config struct {
    BuildHandlerChainsFunc func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler)
}


func NewConfig() *Config {
    return &Config{
            BuildHandlerChainsFunc:      DefaultBuildHandlerChain,
    }
}


func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
    return generic(protect(apiHandler)), generic(audit(apiHandler)) // add filters to handler
}

注意, 这里传递的参数是: s.HandlerContainer.ServeMux, DefaultBuildHandlerChain的参数是apiHandler http.Handler, 前者包含后者interface定义的方法.

  • net/http/server.go
type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

未分类

初始化后, Hnalder 以及 InsecureHandler赋值Container, 然后在new Server前, 将handler放入

&http.Server{
        Addr:           s.InsecureServingInfo.BindAddress,
        Handler:        s.InsecureHandler,   // s.Hanlder for secure
        MaxHeaderBytes: 1 << 20,
}

K8S APISERVER源码: API注册主体流程

基于版本 1.6.7

未分类

k8s使用了go-restful github, 在前面, 已经介绍了container如何初始化的.

这里, 需要关注, api是如何注册进来的. 即, route -> webservice -> container

begin

  • pkg/master/master.go
func (c completedConfig) New() (*Master, error) {

   //  register /api
   m.InstallLegacyAPI(c.Config, c.Config.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider)
   //  register /apis
   m.InstallAPIs(c.Config.APIResourceConfigSource, c.Config.GenericConfig.RESTOptionsGetter, restStorageProviders...)
}

1. /api

  • pkg/master/master.go
func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) {
    legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
    m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo)
  • vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
    s.installAPIResources(apiPrefix, apiGroupInfo)
}

2. /apis

  • pkg/master/master.go
func (m *Master) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) {
    for i := range apiGroupsInfo {
        m.GenericAPIServer.InstallAPIGroup(&apiGroupsInfo[i])   }
}
  • vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
    s.installAPIResources(APIGroupPrefix, apiGroupInfo)
}

3. all to installAPIResources

vendor/k8s.io/apiserver/pkg/server/genericapiserver.go
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
    for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
        apiGroupVersion.InstallREST(s.HandlerContainer.Container)
    }
}
  • vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
    installer := g.newInstaller()
    // 新建一个WebService
    ws := installer.NewWebService()

    // 关键, URL注册, add router into ws
    apiResources, registrationErrors := installer.Install(ws)
    lister := g.ResourceLister
    if lister == nil {
        lister = staticLister{apiResources}
    }
    AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)

    // container.add(webservice)
    container.Add(ws)
    return utilerrors.NewAggregate(registrationErrors)
}

在这里

  1. 新建一个WebService
  2. 由installer.Install(ws)将API 对应的route新建初始化后, 加入到 WebService
  3. 将WebService加入到Container
    完成了router -> webservice -> container的流程

后面, 分析 installer.Install(ws) 具体做了哪些事情(vendor/k8s.io/apiserver/pkg/endpoints/installer.go)

使用open-falcon cAdvisor实现对k8s(kubernetes)集群的监控

1. 前言

当我们的k8s要面临落地时,监控和日志肯定时不可缺少的。它主要为了帮助系统运维人员事前及时预警发现故障,事后通过翔实的数据追查定位问题。

2. 可选方案

  • Heapster(数据采集自cAdvisor)+Influxdb(存储)+Grafana(展示)
    这套方案缺点是没有报警功能

  • Prometheus+Grafana
    参考:http://blog.csdn.net/zqg5258423/article/details/53119009

  • open-falcon+cAdvisor
    本文主要介绍这种方式,官方文档:https://book.open-falcon.org/zh/intro/index.html
    实际案例:https://zhuanlan.zhihu.com/p/27697789

3. openfalcon架构图

未分类

可以看到,它的核心组件是agent,它需要运行在每台node上获取数据,所以对于k8s集群的监控,我们会主要对agent做二次开发,嵌入cAdvisor代码,实现对容器的监控。

4. 涉及的组件及简要说明

  • agent 监控信息上报 需要二次开发
  • transfer 监控信息转发
  • graph 监控信息存储
  • query 监控信息查询
  • hbs 心跳服务器
  • judge 报警事件判断
  • alarm 报警事件处理 需要二次开发
  • sender 报警事件发送 需要二次开发
  • nodata 假数据填充
  • Redis 报警事件队列

5. 监控流程

Agent监控各个主机和容器的信息(嵌入了cadvisor代码),并将数据通过transfer转发存入graph,控制台通过http方式访问query api查询graph获取数据。

6. 报警流程

不再单独部署portal模块,报警策略信息由控制台报警模块提供并写入MySQL的portal数据库,供hbs读取,hbs加载mysql portal库策略数据并关联到endpoint后提供给Judge组件使用,transfer转发到judge的每一条数据会触发相关策略进行判断将结果放入redis,alarm从redis中获取报警event后保存至控制台数据库,以提供控制台
前端展示报警信息

7. 监控系统本身的健康检查

对于k8s集群主机的基础指标监控,容器的基本指标,我们用falcon来监控, 而k8s核心组件和falcon核心组件的可用性监控,用一个独立的工具scripts来监控,它会部署在多个数据中心的多台主机上进行探测。

对监控系统的监控不能再依赖于监控系统, 使用独立工具的好处在于没有依赖,简单稳定,可用性高。而多中心多主机的部署也是为了保证工具的可用性。

解决Kubernetes(k8s) 1.7.3 kube-apiserver频繁异常重启的问题

近期将之前的一个用kube-up.sh安装的Kubernetes 1.3.7的环境更换为最新发布的用kubeadm安装的Kubernetes 1.7.3版本。新版本的安装过程和之前的采用kubeadm安装的k8s 1.5.x、1.6.x版本类似,这里不赘述了。但在安装Dashboard后,发现了一些问题,这里记录一下解决的过程。

一、第一个问题

我们先来做一下回顾。在《解决Kubernetes 1.6.4 Dashboard无法访问的问题》一文中,我们通过把用户admin bind到cluster-admin这个clusterrole角色上使得dashboard得以正常访问。但访问几次后,我发现了一个问题:那就是用safari访问dashboard时,浏览器可以正常弹出鉴权对话框,让我输入用户名和密码;但用chrome访问时,总是无法弹出鉴权对话框,而直接显示如下错误:

User "system:anonymous" cannot get  at the cluster scope.

kube-apiserver身份验证文档中对anonymous requests做了说明:对于没有被其他身份验证方法拒绝的requests,kube-apiserver会为这样的request赋予用户名: system:anonymous和用户group: system:unauthenticated,这个request将继续流向后面的环节:authorization和admission-control,直到被后面的环节拒绝,返回失败应答。这一些都源于k8s 1.6以后的版本中,kube-apiserver的命令行选项:–anonymous-auth的默认值改为了true,即允许anonymous request的存在,因此上面chrome在访问kube-apiserver时,不输入user、password也能继续下面的环节,这就是第一个问题及其原因。

二、关闭匿名请求的身份验证权

解决上面这个问题,最直接的方法就是关闭匿名请求的身份验证权,即不接受匿名请求。我们通过在/etc/kubernetes/manifests/kube-apiserver.yaml中添加下面一行来实现:

spec:
  containers:
  - command:
    - kube-apiserver
    - --anonymous-auth=false

/etc/kubernetes/manifests/kube-apiserver.yaml被修改后,kubelet会重启kube-apiserver。重启后,我再用chrome访问dashboard,身份验证对话框就出现在眼前了。

三、kube-apiserver周期性异常重启

一直以为问题到这里就解决了。但随后又发生了一个更为严重的问题,那就是:kube-apiserver定期重启,并牵连kube-controller-manager和kube-scheduler的status也不正常了。

通过kubectl describe查看状态异常的kube-apiserver pod,发现如下输出:

root@yypdcom2:# kubectl describe pods/kube-apiserver-yypdcom2 -n kube-system|grep health
    Liveness:        http-get https://127.0.0.1:6443/healthz delay=15s timeout=15s period=10s #success=1 #failure=8

可以看到liveness check有8次failure!8次是kube-apiserver的failure门槛值,这个值在/etc/kubernetes/manifests/kube-apiserver.yaml中我们可以看到:

livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 15
      timeoutSeconds: 15

这样,一旦failure次数超限,kubelet会尝试Restart kube-apiserver,这就是问题的原因。那么为什么kube-apiserver的liveness check会fail呢?这缘于我们关闭了匿名请求的身份验证权。还是来看/etc/kubernetes/manifests/kube-apiserver.yaml中的livenessProbe段,对于kube-apiserver来说,kubelet会通过访问: https://127.0.0.1:6443/healthz的方式去check是否ok?并且kubelet使用的是anonymous requests。由于上面我们已经关闭了对anonymous-requests的身份验证权,kubelet就会一直无法访问kube-apiserver的/healthz端点,导致kubelet认为kube-apiserver已经死亡,并尝试重启它。

四、调整/healthz检测的端点

我们既要保留 –anonymous-auth=false,还要保证kube-apiserver稳定运行不重启,我们就需要调整kube-apiserver的livenessProbe配置,将liveness probe的endpoint从

https://127.0.0.1:6443/healthz

改为:

http://127.0.0.1:8080/healthz

具体对/etc/kubernetes/manifests/kube-apiserver.yaml的修改是:

spec:
  containers:
  - command:
    - kube-apiserver
    - --anonymous-auth=false
    ... ...
    - --insecure-bind-address=127.0.0.1
    - --insecure-port=8080

   livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 15
      timeoutSeconds: 15
... ...

我们不再用anonymous-requests,但我们可以利用–insecure-bind-address和–insecure-port。让kubelet的请求到insecure port,而不是secure port。由于insecure port的流量不会受到身份验证、授权等功能的限制,因此可以成功probe到kube-apiserver的liveness,kubelet不会再重启kube-apiserver了。