Kubernets特性介绍

2020/01/02 posted in  Kubernetes

Kubernetes 源于希腊语,意为 “舵手” 或 “飞行员”。它是Google开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理,由于Kubernetes单词中,k和s之间的单词数量为8个,所以也简称k8s。

系统架构

单主集群

单主集群,是集群内只包含一个Control Panel节点的集群,一般用作开发环境,下图是单主集群的结构。

由于主节点只有一个,且work节点无法再主节点宕机后选举产生新的主节点,所有单主集群不适用与产品环境。

高可用集群

高可用集群,是至少包含三个Control Panel节点的集群,Work节点不再直接与某一台主节点的api-server联系,而是通过负载均衡均匀分不到master节点,同时由于etcd节点可以使用外部集群替代,高可用集群又分为内部etcd以及外部etcd集群两种。

内部etcd节点集群

外部etcd节点集群

Kubernets的优势

简化应用程序部署和维护工作,同时最大化利用硬件资源

自动修复

k8s 会自动重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器,并且这些都是在用户无感知的情况下进行的(副本数量需要大于1且有正常运行的容器)。

要想介绍k8s的自动修复,必须要介绍存活探针,k8s正是利用存活探针来检查容器是否还在运行,如果探测失败,k8s将自动重启该容器。

k8s有三种探针机制:

  1. HTTP GET: 针对容器的IP地址,端口以及路径,执行HTTP GET请求,如果收到的返回状态码为2xx或者3xx,则认为探测成功
  2. TCP: 针对Socket通信进行探测,尝试与指定端口建立TCP连接,如果连接成功则探测成功。
  3. Exec: 在容器内执行指定的命令,并检查命令的退出状态码,如果状态码为0,则探测成功。

我们先声明一个拥有两个副本的pod,创建deployment.yaml文件,写入下面的内容

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 2
  selector:
    app: kubia
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
        - name: kubia
          image: luksa/kubia
          livenessProbe:
              httpGet:
                path: /
                port: 8080
          ports:
            - containerPort: 8080

然后利用kubectl创建pod

kubectl create -f deployment.yaml -n default

查看pod状态

ubuntu@k8s-master:~$ kubectl get pods -n default
NAME          READY   STATUS    RESTARTS   AGE
kubia-dvdkl   1/1     Running   0          4m55s
kubia-g7z9g   1/1     Running   0          4m55s

等两个pod都完全运行起来之后,我们先模拟第一种情况,程序异常退出,我们删除掉其中一个Pod

ubuntu@k8s-master:~$ kubectl delete pod kubia-g7z9g -n default
pod "kubia-g7z9g" deleted
ubuntu@k8s-master:~$ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
kubia-dvdkl   1/1     Running   0          7m43s
kubia-jtq6c   1/1     Running   0          39s

可以看到,很快另外一个应用以及重新运行起来了。

再来看另外一种情况,节点故障,我们关闭其中一台node节点

ubuntu@k8s-master:~$ kubectl get nodes
NAME          STATUS     ROLES    AGE     VERSION
k8s-master    Ready      master   26d     v1.16.2
k8s-node-01   Ready      <none>   26d     v1.16.2
k8s-node-02   Ready      <none>   26d     v1.16.2
k8s-node-03   Ready      <none>   26d     v1.16.2
k8s-node-04   Ready      <none>   18d     v1.16.2
k8s-node-05   Ready      <none>   16d     v1.16.2
k8s-node-06   Ready      <none>   14d     v1.16.2
k8s-node-07   Ready      <none>   4d20h   v1.16.2
k8s-node-08   NotReady   <none>   4d19h   v1.16.2

等待几分钟后,查看Pods

ubuntu@k8s-master:~$ kubectl get pods -n default -o wide
NAME          READY   STATUS        RESTARTS   AGE     IP            NODE          NOMINATED NODE   READINESS GATES
kubia-cpmbh   0/1     Pending       0          0s      <none>        k8s-node-06   <none>           <none>
kubia-dvdkl   1/1     Terminating   1          4d19h   10.244.8.44   k8s-node-08   <none>           <none>
kubia-jtq6c   1/1     Running       0          4d19h   10.244.7.5    k8s-node-07   <none>           <none>

针对节点故障,k8s并不会把出问题的节点上的所有Pod都迁移到别的Pod上,而是创建新的Pod,原来的Pod仍然保留,只是状态不再是Ready,如果节点恢复,原来的Pod状态也会恢复。

服务发现

我们先为kubia应用创建一个服务,创建service.yaml,写入下面的内容

kind: apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080

执行kubectl create -f service.yaml -n default创建服务

ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/service.yaml -n default
service/kubia created

查看service状态

ubuntu@k8s-master:~/k8s-dev$ kubectl get svc -n default
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   26d
kubia        ClusterIP   10.107.59.19   <none>        80/TCP    27s

k8s的服务发现分为两种,集群内部访问和集群外部访问

集群内部

可以通过内置的DNS用服务名的方式访问集群内部的应用,同时也能利用环境变量获取开放的端口。

通过环境变量访问服务

当pod创建时,k8s会把当前命名空间内已存在的服务已环境变量的方式导入到pod当中,所以只要你的pod晚于service的创建,pod里的进程就可以根据环境变量获得服务的IP和端口号。
我们先查看pod早于service创建的情况

ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-8fffd4bff-k4tk4 env -n default
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-8fffd4bff-k4tk4
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root

然后我们删除pod,让他自动修复

kubectl delete pod --all -n default

查看新的pod

ubuntu@k8s-master:~/k8s-dev$ kubectl get pods -n default
NAME                    READY   STATUS    RESTARTS   AGE
kubia-8fffd4bff-622rl   1/1     Running   0          44s
kubia-8fffd4bff-6h64s   1/1     Running   0          44s

查看新pod的环境变量

ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-8fffd4bff-622rl env -n default
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-8fffd4bff-622rl
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_SERVICE_HOST=10.107.59.19
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBIA_PORT=tcp://10.107.59.19:80
KUBIA_PORT_80_TCP=tcp://10.107.59.19:80
KUBIA_PORT_80_TCP_ADDR=10.107.59.19
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root

对比两种情况,可以发现,KUBIA_SERVICE_HOSTKUBIA_SERVICE_PORT已经存在于环境变量当中了。

通过DNS访问服务

在k8s内部,借助CoreDNS,一旦一个服务创建好,我们可以用hostname.namespace.svc.cluster.local这个域名来获取到服务的IP地址,如果应用和服务同一个集群内部,甚至在同一个命名空间下,我就可以直接用hostname访问该服务
利用service名称访问服务

ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-8fffd4bff-622rl curl http://kubia -n default
You've hit kubia-8fffd4bff-622rl
kubectl exec kubia-8fffd4bff-622rl curl http://kubia.default.svc.cluster.local -n default
You've hit kubia-8fffd4bff-6h64s

那么问题来了,利用DNS的方式访问服务确实很方便,但是DNS只能解决IP,针对端口问题怎么办?
针对这种情况我们的处理办法是固定端口,应为在k8s内部大部分应用独享一个IP的所有端口,所以不存在端口冲突的问题,我们可以针对不同的协议约定一个统一的端口,这样应用就不需要知道具体应用的端口了。

常用协议默认端口:
http: 80
https: 443
mysql: 3306
redis: 6379
gRPC: 9000

集群外部

k8s提供了NodePort,LoadBlancer,Ingres三种服务暴露的方式,应用于各种需要对外暴露服务的场景。
其中LoadBlancer属于云服务商特有的功能,如果你是私有集群,大概率是用不了这个功能了。

NodePort

NodePort的原理时,在所有Node节点上监听一个端口(所有node节点端口相同),并将传入的数据转发到对应的服务上。

我们修改之前的kubia应用的service.yaml

kind: apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  type: NodePort
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 20000

这次我们指定type类型为NodePort,同时注明nodePort的端口号为20000
我们更新看看效果

ubuntu@k8s-master:~/k8s-dev$ kubectl delete svc kubia -n default
service "kubia" deleted
ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/service-nodeport.yaml -n default
service/kubia created

查看新的service

ubuntu@k8s-master:~/k8s-dev$ kubectl get svc -n default
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        26d
kubia        NodePort    10.111.0.106   <none>        80:30123/TCP   38s

对比之前的service,新的kubia服务多了一个30123端口,我们可以通过集群任意node节点的ip地址访问这个端口

ubuntu@k8s-master:~/k8s-dev$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-622rl
Ingress

ingress功能在k8s默认集群里没有提供,需要手动安装开启。ingress可以让应用在http层暴露给外部,通过配置不同的host来指向不同的服务

我们先为kubia这个应用创建一个ingress资源ingress.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.dev.youxuetong.com
    http:
      paths:
        - path: /
          backend: 
            serviceName: kubia
            servicePort: 80

创建ingress资源并查看

ubuntu@k8s-master:~$ kubectl create -f k8s-dev/kubia/ingress.yaml -n default
ingress.networking.k8s.io/kubia created
ubuntu@k8s-master:~$ kubectl get ingress -n default
NAME    HOSTS                      ADDRESS   PORTS   AGE
kubia   kubia.dev.youxuetong.com             80      13s

我们试试通过域名访问这个服务

➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-622rl

负载均衡

k8s可以将网络流量随机分发(按照一定规则,在最新的1.17中,会选择一个最短路由的pod节点)到pod节点上。

我们可以看到每次访问同一服务,都可能会落到不同的pod节点上

➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-622rl
➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-622rl
➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-622rl
➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-6h64s
➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-6h64s
➜  ~ curl http://kubia.dev.youxuetong.com
You've hit kubia-8fffd4bff-6h64s

如果有些特殊应用,希望同一个用户的访问能指向同一个pod,比如处理socket长连接的应用,可以通过更改service的会话亲和性来达到效果

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  sessionAffinity: ClientIP
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080

重新创建service后我们来看看效果

ubuntu@k8s-master:~/k8s-dev$ kubectl delete svc kubia -n default
service "kubia" deleted
ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/service-session.yaml -n default
service/kubia created

利用curl访问

ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s
ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s
ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s
ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s
ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s
ubuntu@k8s-master:~$ curl http://127.0.0.1:30123
You've hit kubia-8fffd4bff-6h64s    

配置文件

k8s支持将一些敏感信息以及相关应用的配置文件单独存放,做到和应用无关。

创建ConfigMap,configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: kubia
  labels:
    app: kubia
   
data:
  config.json: |-
    {
      "service":{
        "host":"http://kubia.dev.youxuetong.com",
        "ip":"10.9.22.1",
        "port":80
      }    
    }
  config.yaml: |-
    service:
      host: "http://kubia.dev.youxuetong.com"
      ip: "10.9.22.1"
      port: 80

修改deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      volumes:
        - name: config
          configMap:
            name: kubia
      containers:
        - name: kubia
          image: luksa/kubia
          livenessProbe:
              httpGet:
                path: /
                port: 8080
          volumeMounts:
          - name: config
            mountPath: /opt/config
            readOnly: true
          ports:
            - containerPort: 8080

生成configmap,删除之前的deployment,创建新的

ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/configmap.yaml -n default
configmap/kubia created
ubuntu@k8s-master:~/k8s-dev$ kubectl delete deployment kubia -n default
deployment.apps/kubia deleted
ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/deployment-config.yaml -n default
deployment.apps/kubia created

我们进入pod中查看配置文件是否挂载成功

ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-65cbb6d475-c5pqk ls /opt/config -n default
config.json
config.yaml
ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-65cbb6d475-c5pqk cat /opt/config/config.json -n default
{
  "service":{
    "host":"http://kubia.dev.youxuetong.com",
    "ip":"10.9.22.1",
    "port":80
  }
}
ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-65cbb6d475-c5pqk cat /opt/config/config.yaml -n default
service:
  host: "http://kubia.dev.youxuetong.com"
  ip: "10.9.22.1"
  port: 80

密钥管理

密钥管理和配置文件类似,只是密钥当中存储的的是一些敏感信息而已。
我们以https证书举例,当我们在k8s当中,想给我们的域名绑上https证书的时候
先创建证书secret,需要实现准备好证书的key和证书文件

ubuntu@k8s-master:~/k8s-dev/ssl-dev.youxuetong.com$ kubectl create secret tls dev.youxuetong.com --cert=server.crt --key=server.key -n default
secret/dev.youxuetong.com created

这时我们如果想给之前通过ingress暴露的域名http://kubia.dev.youxuetong.com加上https证书,只需要修改ingress.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.dev.youxuetong.com
    http:
      paths:
        - path: /
          backend: 
            serviceName: kubia
            servicePort: 80
  tls:
  - hosts:
    - kubia.dev.youxuetong.com
    secretName: dev.youxuetong.com

增加tls字段,为kubia.dev.youxuetong.com 绑上之前创建好的secret
我们利用curl查看https证书有没有生效

ubuntu@k8s-master:~/k8s-dev$ curl -k -v https://kubia.dev.youxuetong.com/
*   Trying 125.46.60.5...
* Connected to kubia.dev.youxuetong.com (125.46.60.5) port 443 (#0)
* found 149 certificates in /etc/ssl/certs/ca-certificates.crt
* found 596 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_256_GCM_SHA384
*    server certificate verification SKIPPED
*    server certificate status verification SKIPPED
*    common name: *.dev.youxuetong.com (matched)
*    server certificate expiration date OK
*    server certificate activation date OK
*    certificate public key: RSA
*    certificate version: #3
*    subject: C=CN,ST=HeNan,L=ZhengZhou,O=JiangShan Technology Co.Ltd,OU=JiangShan Technology Co.Ltd,CN=*.dev.youxuetong.com,EMAIL=yxt@youxuetong.com
*    start date: Fri, 03 Jan 2020 02:27:09 GMT
*    expire date: Mon, 17 May 2021 02:27:09 GMT
*    issuer: C=CN,ST=henan,L=zhengzhou,O=Jiangshan Co.Ltd,OU=Jiangshan Co.Ltd,CN=Jiangshan Co.Ltd,EMAIL=yxt@youxuetong.com
*    compression: NULL
* ALPN, server accepted to use http/1.1
> GET / HTTP/1.1
> Host: kubia.dev.youxuetong.com
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.15.8.2
< Date: Tue, 14 Jan 2020 06:26:32 GMT
< Transfer-Encoding: chunked
< Connection: keep-alive
< Strict-Transport-Security: max-age=15724800; includeSubDomains
<
You've hit kubia-65cbb6d475-c5pqk

统一存储

k8s允许您自由选择的适合自己存储系统,无论是本地存储、远程目录共享还是云服务商提供的网络磁盘,并提供统一的分配和挂载方案。
存储分为静态存储和动态存储

静态存储

静态存储需要事先在node上创建目录,分配空间,然后将目录挂载到pod上,和docker的目录挂载原理一样。这样的缺点就是每创建一个应用之前还需要手动创建目录,同时这样生成的pod的流动性就很差了,所以大部分情况下我们不会使用。

动态存储

无需实现绑定,应用程序只需创建一个存储卷声明,声明所需磁盘空间大小和类型,即可自动完成创建和绑定。
我们先创建一个持久卷声明pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: kubia-pvc
  labels:
    app: kubia
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

然后在deployment上绑定已经创建的pvc

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 2
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      volumes:
        - name: kubia-data
          persistentVolumeClaim:
            claimName: kubia-pvc
      containers:
        - name: kubia
          image: luksa/kubia
          livenessProbe:
              httpGet:
                path: /
                port: 8080
          volumeMounts:
          - name: kubia-data
            mountPath: /data
          ports:
            - containerPort: 8080

重新创建各项资源

ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/pvc.yaml -n default
persistentvolumeclaim/kubia-pvc created
ubuntu@k8s-master:~/k8s-dev$ kubectl get pvc -n default
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
kubia-pvc   Bound    pvc-bb91d0ac-5076-41ba-b1fa-beb26473a0dc   10Gi       RWO            nfs-client     29s
ubuntu@k8s-master:~/k8s-dev$ kubectl delete deployment kubia -n default
deployment.apps "kubia" deleted
ubuntu@k8s-master:~/k8s-dev$ kubectl create -f kubia/deployment-pvc.yaml -n default
deployment.apps/kubia created

我们进入目录查看目录是否挂载成功,并创建一个文件

ubuntu@k8s-master:~/k8s-dev$ kubectl exec kubia-5bb69778fc-n2v6k touch /data/readme.md -n default

去NFS服务器查看文件是否成功创建

ubuntu@K8S-NFS:/data$ cd default-kubia-pvc-pvc-bb91d0ac-5076-41ba-b1fa-beb26473a0dc/
ubuntu@K8S-NFS:/data/default-kubia-pvc-pvc-bb91d0ac-5076-41ba-b1fa-beb26473a0dc$ ll
total 8
drwxrwxrwx  2 root root 4096 Jan 14 00:54 ./
drwxrwxrwx 21 root root 4096 Jan 14 00:51 ../
-rw-r--r--  1 root root    0 Jan 14 00:54 readme.md

统一日志

利用EFK(elasticsearch,fluentd,kibaba)很容易做到对这个集群的日志跟踪。

在k8s里,Fluentd会被安装在每个Node节点上,同时会挂载当前机器的/var/lib/docker/containers目录,由于docker所有的标准输出都会在该目录下记录日志,所以Fluentd可以很容易的做到将整个集群上所允许的应用的日志统一发送到后端。

统一监控

利用Metric-Server可以获取pod集群的资源占用情况的数据,在加上Prometheus和Grafana很容搭建一套集群的监控报警系统。

滚动升级&回滚

k8s提供逐步更新应用程序的能力,并提供不同的升级策略,让用户无感知情况下升级或者回滚应用程序。

自动伸缩

k8s不仅提供手动的应用程序扩容机制,同时还可以通过监视应用程序的CPU使用率或其他度量增长时自动对应用程序进行扩容,已应用短期的高并发请求。