主要内容
简介
容器编排指对容器的创建、部署、销毁、网络处理、故障转移等管理行为,Kubernetes的容器编排能力强大,上手简单。
部署应用
准确来说是部署容器,前面说过,Pod是Kubernetes的最小管理单元,但我们一般不会直接管理Pod,通常是利用Controller作为媒介,这些Controller包括Deployment、ReplicaSet、DaemonSet、Job等。
使用Deployment
有两种方式使用Deployment,一种是直接使用命令行创建,另一种是通过yaml文件创建,即Kubernetes对象。
- 通过命令行创建Deployment
以前我们
Docker run
命令运行一个容器,Deployment的创建非常类似,使用的是kubectl run
。例如创建两个副本的nginx应用1234k8s@centos7-server:~$ kubectl run webapp --image=nginx --replicas=2kubectl run --generator=deployment/apps.v1beta1 is DEPRECATED and will be removed in a future version. Use kubectl create instead.deployment.apps/webapp created很简单就创建了一个Deployment,
webapp
是deployment的名字,但它也作为lable
使用,label后面会说。正如提示信息中显示,这种方式已被标记为deprecated,即不建议使用。创建好Deployment,查看它是否正常运行1234k8s@centos7-server:~$ kubectl get deployment -o wideNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTORwebapp 2 2 2 2 5m4s webapp nginx run=webapp也可以检查Deployment中的pod运行情况
12345k8s@centos7-server:~$ kubectl get pods -l run=webapp -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODEwebapp-64c7ffd54d-sw6kq 1/1 Running 0 5m21s 10.244.1.8 centos74vm <none>webapp-64c7ffd54d-vt2lw 1/1 Running 0 5m20s 10.244.2.6 dell-ubuntu1804 <none> - 通过yaml文件创建
这次我们通过http镜像创建web服务,同样是两个副本,下面是简单的yaml格式
httpd.yaml:
123456789101112131415apiVersion: apps/v1beta1kind: Deploymentmetadata:name: httpdspec:replicas: 2template:metadata:labels:app: httpserverspec:containers:- name: httpdimage: httpd文件准备好后通过kubectl apply命令创建Deployment
123k8s@server:~$ kubectl apply -f httpd.yamldeployment.apps/httpd created通过
kubectl describe
命令查看pod的详细信息,其中一部分信息如下123456789101112131415k8s@server:~$ kubectl describe pod httpdName: httpd-69f47fb867-mrvn5Namespace: defaultPriority: 0PriorityClassName: <none>Node: ubuntu-host2/192.168.11.163Start Time: Tue, 09 Oct 2018 22:00:42 -0400Labels: app=httpserverpod-template-hash=69f47fb867Annotations: <none>Status: RunningIP: 10.244.2.13Controlled By: ReplicaSet/httpd-69f47fb867....从Controlled By可以看出,这些Pod由ReplicaSet控制,而由此之中我们都没有使用过ReplicaSet,证明Deployment管理着ReplicaSet。
describe
命令可以查看很多资源的详细信息,不仅仅限于Pod。
两种部署方式各有好处:
- 命令行适合于临时测试
- yaml适合产品部署
- 命令行方式部署方式不可追踪
- yaml方式可追踪
使用DaemonSet
Deployment不限制Pod在Host中的数量,而DaemonSet限制一个Host上面只能运行Pod的单个实例,即不能有副本的Pod。Kubernetes必要组件就运行有daemonset:flannel组件和proxy组件
1 2 3 4 5 6 7 8 9 |
k8s@server:~$ kubectl get daemonset --namespace=kube-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-flannel-ds-amd64 3 3 3 3 3 beta.kubernetes.io/arch=amd64 4d19h kube-flannel-ds-arm 0 0 0 0 0 beta.kubernetes.io/arch=arm 4d19h kube-flannel-ds-arm64 0 0 0 0 0 beta.kubernetes.io/arch=arm64 4d19h kube-flannel-ds-ppc64le 0 0 0 0 0 beta.kubernetes.io/arch=ppc64le 4d19h kube-flannel-ds-s390x 0 0 0 0 0 beta.kubernetes.io/arch=s390x 4d19h kube-proxy 3 3 3 3 3 <none> 4d19h |
按照这个特征,daemonset非常适合运行用于日志收集和容器监控的Pod。
以日志收集系统ELK Stack为例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: elk-stack spec: template: metadata: labels: run: elk # 这个label用于被Service匹配 spec: containers: - name: elk image: sebp/elk ports: - containerPort: 5601 name: kibana #hostPort: 5601 - containerPort: 9200 name: elasticsearch #hostPort: 9200 - containerPort: 5044 name: logstash #hostPort: 5044 |
查看系统现有的daemonset
1 2 3 4 5 6 7 8 |
k8s@server:~$ kubectl get daemonset NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE elk-stack 2 2 2 0 2 <none> 137m k8s@server:~$ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE elk-stack-24sr8 1/1 Running 12 138m 10.244.1.19 ubuntu-host1 <none> elk-stack-clplq 1/1 Running 13 138m 10.244.2.16 ubuntu-host2 <none> |
使用Service
Pod很可能会因为某种原因而发生故障,为了保证服务的高可用,Controller就会创建新的Pod替换掉发生故障的Pod。这也就产生一个问题:Pod的IP地址发生变化,客户端如何正确访问Pod提供的服务。
答案是使用Service。
Service也有自己的IP地址,它会根据label来连接对应的Pod,一旦Service和Pod建立好逻辑关系之后,我们就可以通过Service的IP地址来访问与之连接的Pod。Pod的地址会发生变化,但Service的则不会。
以上面的elk stack为例子,一般来说,我们都是通过kibana的web端口访问数据,也就是说需要开放5601端口给外网
elk-svc.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
apiVersion: v1 kind: Service metadata: name: elk-svc spec: type: NodePort selector: run: elk ports: - protocol: TCP nodePort: 30000 port: 80 targetPort: 5601 |
准备好后通过kubectl apply
创建Service
1 2 3 |
k8s@server:~$ kubectl apply -f elk-svc.yaml service/elk-svc created |
外网访问Service的三种方式
这三种方式分别是:
- ClusterIP 默认Service的行为,只能在cluster内部访问Pod
- NodePort 把外部到Host的端口流量转发到Pod中,实现外网访问Pod
- LoadBalancer Service 利用 cloud provider 特有的 load balancer 对外提供服务,cloud provider 负责将 load balancer 的流量导向 Service。目前支持的 cloud provider 有 GCP、AWS、Azur 等。
上面的例子中Service的type设置了NodePort,这样就可以通过Host的IP地址访问到Pod。使用NodePort的时候我们设置了三个端口,分别是
- nodePort host开放的端口
- port clusterIP开放的端口
- targetPort Pod开放的端口
实际上,NodePort方式是在clusterIP的基础上发展出来的,通过host上的端口访问clusterIP的端口,最后clusterIP访问Pod暴露的端口。
检查现有的service
1 2 3 4 5 6 |
k8s@server:~$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE elk-svc NodePort 10.109.92.195 <none> 80:30000/TCP 24m httpd-svc NodePort 10.110.4.173 <none> 8080:30457/TCP 2d22h kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d |
通过DNS访问Service
在Cluster中,除了可以使用ClusterIP访问service外,还可以使用kubernetes提供的DNS访问,该功能由core-dns组件提供。Cluster 中的 Pod 可以通过 <SERVICE_NAME>.<NAMESPACE_NAME>
访问 Service
如果默认名称空间default中运行着nginx,那么nginx就可以通过kubernetes.default访问默认的Service,由于它们在同一个namespace中,因此可以省略.default,直接使用kubernetes就可以了
1 2 3 4 |
[k8s@centos7-server ~]$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5h45m |
而不同名称空间的Pod/Service使用DNS,必须通过<SERVICE_NAME>.<NAMESPACE_NAME>
的方式,这个例子是kubernetes.default。
控制Pod的运行位置
默认情况下,Scheduler会将Pod分配到所有可用的节点,但master节点除外。因为master设置了一个taint
1 2 3 4 5 6 7 8 |
k8s@server:~$ kubectl describe node server Name: server Roles: master ..... CreationTimestamp: Fri, 05 Oct 2018 03:33:23 -0400 Taints: node-role.kubernetes.io/master:NoSchedule ..... |
node-role.kubernetes.io/master:NoSchedule
表示Pod不会被分配到该节点。
我们可以手动控制Pod的运行位置,例如CPU密集型的任务部署在CPU性能高的节点,硬盘IO密集型的任务部署在硬盘性能高的节点。
首先, 为目标节点添加一个label
1 2 3 4 5 6 |
k8s@server:~$ kubectl label nodes ubuntu-host1 performance=high node/ubuntu-host1 labeled k8s@server:~$ kubectl get node ubuntu-host1 --show-labels NAME STATUS ROLES AGE VERSION LABELS ubuntu-host1 Ready <none> 5d1h v1.12.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=ubuntu-host1,performance=high |
其次,通过nodeSelector分配节点
修改yaml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: httpd spec: replicas: 2 template: metadata: labels: app: httpserver spec: containers: - name: http2 image: httpd nodeSelector: performance: high |
重新部署应用
1 2 |
kubectl apply -f httpd.yaml |
检查运行情况
1 2 3 4 5 |
k8s@server:~$ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE httpd-65c4ddd884-nj4qx 1/1 Running 0 54s 10.244.1.28 ubuntu-host1 <none> httpd-65c4ddd884-r8wbw 1/1 Running 0 54s 10.244.1.27 ubuntu-host1 <none> |
因为ubuntu-host1标记了高性能(performance=high),因此两个副本都运行在这个节点上面,达到预期效果。
删除label
通过下面的方式删除label(在key后面添加一个横线)
1 2 |
k8s@server:~$ kubectl label nodes ubuntu-host1 performance- |
删除label后Pod不会被迁移,依然运行在这个节点,除非重新部署。
应用的弹性伸缩
通过replicas参数,可以在线动态增加或减少Pod的数量,以上面的yaml文件为例,一开始创建了2个副本的应用,现在增加到5个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: httpd spec: replicas: 5 #更新2为5 template: metadata: labels: app: httpserver spec: containers: - name: httpd image: httpd |
然后执行apply命令
1 2 3 |
k8s@server:~$ kubectl apply -f httpd.yaml deployment.apps/httpd configured |
现在deployment中的Pod数量应该就是5
1 2 3 4 |
k8s@server:~$ kubectl get deployment httpd NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE httpd 5 5 5 5 7m44s |
减少Pod数量同理,只需要设置replicas设为相应的值然后执行apply命令即可。
滚动更新
滚动更新指的是对镜像版本的更新,假设原先服务器上部署的nginx应用如下
nginx.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx spec: replicas: 2 template: metadata: labels: app: httpserver spec: containers: - name: nginx image: nginx:1.14 |
过了一段时间之后,需要把nginx更新到1.15,只需要把nginx的镜像版本改为对应版本,然后执行apply命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx spec: replicas: 2 template: metadata: labels: app: httpserver spec: containers: - name: nginx image: nginx:1.15 #版本更新到1.15 |
通过输出可知,kubernetes每次执行一个更新,假如前面的更新失败,更新停止
1 2 3 4 5 |
k8s@server:~$ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-bbcdffc78-f2xwz 0/1 ContainerCreating 0 4s nginx-bbcdffc78-zx4dv 1/1 Running 0 10s |
更新策略
虽然不可以直接设置每次更新的副本数量,但是可以控制副本更新时的可用数量。这两个参数是
- maxSurge 滚动更新过程中副本总数可超过
DESIRED
的上限(向上取整)。可以是数值或百分比,默认是25% - maxUnavailable 更新过程中不可用的Pod的最大数量(向下取整)。可以是数值或百分比,默认是25%
具体配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx spec: strategy: #更新策略 rollingUpdate: maxSurge: 30% maxUnavailable: 30% replicas: 10 template: metadata: labels: app: httpserver spec: containers: - name: nginx image: nginx:1.15 |
执行相关命令获取输出信息
1 2 3 4 5 6 |
k8s@server:~$ kubectl apply -f nginx-1.15.yaml --record deployment.apps/nginx configured k8s@server:~$ kubectl get deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 10 13 6 7 42s |
由于maxSurge和maxUnavailable的值都设置为30%,因此更新情况如下:
- Pod的数量马上增加30%,原先有10个,即多了3个,所以CURRENT是13
- 不可用的Pod数量只能是3个(30%),因此10-3=7,所以AVAILABLE值为7
结论:
maxSurge
值越大,初始创建的新副本数量就越多;maxUnavailable
值越大,初始销毁的旧副本数量就越多。
回滚
在测试过程中,我们可能经常会在不同版本之间切换,kubernetes提供了版本回滚支持。
当使用kubectl apply命令的时候,通过--record
参数,可以让kubernetes记录下每次更新的版本信息,这样就可以很方便地使用回滚功能。例如有两个Deployment文件,对应的nginx版本是1.14和1.15
1 2 3 |
k8s@server:~$ ls nginx* nginx-1.14.yaml nginx-1.15.yaml |
我们先部署1.14版本的
1 2 3 4 5 6 7 |
k8s@server:~$ kubectl apply -f nginx-1.14.yaml --record deployment.apps/nginx created k8s@server:~$ kubectl rollout history deployment deployment.extensions/nginx REVISION CHANGE-CAUSE 1 kubectl apply --filename=nginx-1.14.yaml --record=true |
通过--record
参数,kubernetes已经记录下了部署该应用的对应文件。
现在我们把nginx更新到1.15
1 2 3 4 5 6 7 8 |
k8s@server:~$ kubectl apply -f nginx-1.15.yaml --record deployment.apps/nginx configured k8s@server:~$ kubectl rollout history deployment deployment.extensions/nginx REVISION CHANGE-CAUSE 1 kubectl apply --filename=nginx-1.14.yaml --record=true 2 kubectl apply --filename=nginx-1.15.yaml --record=true |
现在版本变更里面有两个记录。
假如项目需要进行回滚,那么通过下面的命令即可
1 2 3 4 5 6 |
k8s@server:~$ kubectl rollout undo deployment nginx --to-revision=1 #回滚到第一版次 deployment.extensions/nginx k8s@server:~$ kubectl get deployment -o wide #检查回滚结果 NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR nginx 2 2 2 2 5m nginx nginx:1.14 app=httpserver |
nginx成功退回到1.14版本。
健康检查
kubernetes默认根据容器中CMD或ENTRYPOINT的返回值来检查容器的健康状况,如果它们返回0,则容器正常,非0则表示容器状态异常,异常的容器是否需要重启,还需要看restartPolicy
。
kubernetes提供了两种自定义的方式检查容器的健康状况:
- liveness探测 判断容器是否需要重启以实现自愈
- readiness探测 判断容器是否已经准备好对外提供服务,如果探测失败,service不会转发流量给此容器
如果没有对它们进行配置,使用默认健康检查。
Liveness探测
通过以下文件创建一个Pod, 通过关键字livenessProbe
设置liveness探测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
apiVersion: v1 kind: Pod metadata: name: liveness labels: test: liveness spec: restartPolicy: OnFailure containers: - name: liveness image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 10 periodSeconds: 5 |
Pod的相关设定:
- 重启策略(restartPolicy):OnFailure 失败的时候重启
- liveness探测: 如果/tmp/healthy文件存在,则说明Pod健康,否则需要重启
- initialDelaySeconds 10表示容器启动10秒后进行健康检查
- periodSeconds 5表示每5秒执行一次检查
Readiness探测
Readiness探测使用和Liveness探测一样,只不过把livenessProbe
改为readinessProbe
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
apiVersion: v1 kind: Pod metadata: name: liveness labels: test: liveness spec: restartPolicy: OnFailure containers: - name: liveness image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 readinessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 10 periodSeconds: 5 |
当Readiness探测失败(/tmp/healthy不存在的时候),那么Service就不会把流量转发给这个Pod,保证了服务的可靠性。
httpGet
除了使用exec执行特定命令外,Kubernetes还提供了httpGet
方案,用于向指定url发起请求,通过返回的状态码检测页面是否可用。通常成功的状态码在200~400之间,但不包含400。
下面的例子创建了一个Deployment和对应的Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: web spec: replicas: 3 template: metadata: labels: run: web spec: containers: - name: web image: nginx ports: - containerPort: 8080 readinessProbe: httpGet: scheme: HTTP path: /healthy port: 8080 initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: web-svc spec: selector: run: web ports: - protocol: TCP port: 8080 targetPort: 80 |
Readiness探测使用httpGet方式访问uri地址/healthy
,通过返回的状态码获取地址的可用性,当状态码在非200~400之间,表示容器还没有就绪,Service不会将流量转发给这个Deployment。直到检测周期内状态码变为可用,Service才会把流量发给它。
对于/healthy
这个uri,可以编写自己的检测逻辑,并根据这个逻辑返回状态码,例如,healty脚本检测数据库是否可用,如果可用则返回状态码200,否则不可用。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 伪代码 <?php $con = mysql_connect("localhost","peter","abc123"); if (!$con) { header("HTTP/1.1 200 OK") }else { header("HTTP/1.1 400 Bad Request") } ?> |
转载请注明:Pure nonsense » Kubernetes容器编排