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有三种探针机制:
HTTP GET
: 针对容器的IP地址,端口以及路径,执行HTTP GET请求,如果收到的返回状态码为2xx或者3xx,则认为探测成功TCP
: 针对Socket通信进行探测,尝试与指定端口建立TCP连接,如果连接成功则探测成功。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_HOST
和KUBIA_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暴露的域名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使用率或其他度量增长时自动对应用程序进行扩容,已应用短期的高并发请求。