本文采用本地k8s v1.10.3版本开发,如果还没有搭建可参照 kubernetes学习笔记 (一):搭建本地k8s开发环境进行搭建
搭建本地Docker镜像仓库
docker pull docker.io/registry
拉取registry的镜像到本机docker run -d -p 5000:5000 --name=registry --restart=always --privileged=true --log-driver=none -v /home/data/registrydata:/tmp/registry registry
运行刚刚拉取的registry- 在Docker for Mac中找到Docker -> Preferences -> Daemon,在insecure registries中填入自己刚刚搭建的本地仓库 http://localhost:5000
准备一个Docker镜像
- 随意使用什么语言写一个http接口,例如Nodejs:
- @Get('whoami')
- async whoAmI() {
- debug('whoAmI called with %O', {});
-
- return { serverType: Config.SERVER_TYPE };
- }
- 复制代码
- 编写对应的DockerFile
- FROM node:8
- WORKDIR /root
-
- RUN mkdir -p /root/app
-
- COPY package.json /root/app/
- COPY dist /root/app/dist
-
- WORKDIR /root/app
-
- RUN npm config set registry https://registry.npm.taobao.org/ ;\
- npm config set disturl https://npm.taobao.org/dist/ ;\
- npm i --production
-
- CMD [ "node", "/root/app/dist/main.js" ]
-
- EXPOSE 80
-
- 复制代码
- 编译、打标签,并推送到本地仓库中
- echo 'Building to dits ...'
- tsc
-
- echo 'Docker building image ...'
- docker build --rm -t localhost:5000/gateapp:0.0.1 .
-
- echo 'Pushing ...'
- docker push localhost:5000/gateapp
-
- echo 'Docker list images ...'
- docker images
- 复制代码
最后你会看到localhost:5000/gateapp:0.0.1的镜像已经在你的机器中啦,下面会用到
Kubernetes中的重要概念
- Cluster: Cluster是计算、存储和网络资源的集合,Kubernetes利用这些资源运行各种基于容器的应用
- Master: Master是Kubernetes的大脑,它的主要职责是调度,即决定将应用放在那里运行。
- Node: Node的职责是运行容器应用。Node由Master管理,Node负责监控并汇报容器的状态,同时根据Master的要求管理容器的生命周期。
- Pod: Pod是Kubernetes中最小工作单元。每个Pod包含一个或多个容器。Pod中的容器会作为一个整体被Master调度到一个Node上运行。
- Controller:Kubernetes通常不会直接创建Pod,而是通过Controller来管理Pod。Controller中定义了Pod的部署特性,比如有几个副本、在什么样的Node上运行等。为了满足不同的业务场景,Kubernetes提供了多种Controller,包括Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job等,后面会逐个学习这些Controller
- Service:Service定义外界访问一组特定Pod的方式。
创建Deployment部署应用
- 新建一个yaml文件,如:
- apiVersion: extensions/v1beta1 # kubectl api的版本
- kind: Deployment # kubernetes的资源类型
- metadata:
- name: gate-deployment-dev
- spec:
- replicas: 2 # 运行的Pod副本数量
- template:
- metadata:
- labels:
- app: gate-app-dev
- spec:
- containers:
- - name: gateapp
- image: localhost:5000/gateapp:0.0.1 # Docker镜像地址(上面创建的)
- env: # 镜像启动时的环境变量
- - name: NODE_ENV
- value: 'development'
- - name: HTTP_PORT
- value: '80' # 容器http的端口 需要自己代码中实现
- 复制代码
-
通过
kubectl apply -f xxx.yaml
就可以部署上面的Deployment的了,也可以打开Kubernetes的Dashboard点击创建,输入yaml文件中的内容进行创建。经过一段时间后,在Dashboard面板中可以看到,1个部署 gate-deployment-dev,一个副本集 gate-deployment-dev-775d556ffb,两个容器组 gate-deployment-dev-775d556ffb-2tlgn、gate-deployment-dev-775d556ffb-sfqrq,可以点击查看每个资源的信息,大部分内容都是自解释的。在部署Deployment后,Kubernetes大致执行了以下几个过程:
- 用户创建Deployment
- Deployment创建了一个副本集(ReplicaSet)gate-deployment-dev-775d556ffb
- ReplicaSet创建了两个Pod,与我们定义的replicas: 2 一致
创建Service
此时上面的镜像提供的接口还不能供外界访问,需要创建一个对应的Service
- 新创建一个yaml文件或者在上一个yaml文件中添加 --- 隔开
- 编辑以下内容:
- apiVersion: v1 # kubectl api的版本
- kind: Service # kubernetes的资源类型
- metadata:
- name: gate-svc-dev
- spec:
- type: NodePort # service的类型 ClusterIp类型 只有Cluster内部节点和Pod可以访问 NodePort Cluster外部可以通过<NodeIp>:<NodePort>访问 LoadBalancer负载均衡
- selector:
- app: gate-app-dev # 与上面的template中定义的label一致
- ports:
- - protocol: TCP # 只有TCP 或 UDP
- port: 80 # clusterIp 监听的端口
- nodePort: 30000 # Node 监听的端口取值范围 30000-32767
- targetPort: 80 # Pod 监听的端口
- 复制代码
-
kubectl apply -f xxx.yaml
或者在Dashboard中创建 -
观察Dashboard面板,会发现多了一个名字叫gate-svc-dev的服务,提供了监听了30000端口
-
curl http://localhost:30000/whoami
可以看到接口已经可以访问了需要注意的是:
-
为什么需要Service?
Deployment等其他Controller动态创建和销毁Pod来保证应用的健壮性,也就是Pod是脆弱的,应用是健壮的,我们不该期望Pod的健壮性。每个Pod都有自己的ClusterIp地址,当Pod发生故障被新的Pod替代时,ClusterIp很有可能发生变化,所以如果直接让外界访问Pod就有问题了。
-
Service和Pod如何映射?
通过上面的yaml定义可以看出,Service通过 label标签选择器选择对应的一堆Pod。当请求被发送到Service上时,Service采用了某种分配策略把流量转发到了某一个Pod上面进行处理。
-
Service有哪些类型?
- ClusterIp:Service通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可以访问,这是默认的类型。
- NodePort: Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过
<NodeIp>:<NodePort>
访问Service - LoadBalancer: cloud provider特有的对外提供服务,后续线上部署时会讲到
-
滚动更新
滚动更新是一次只更新一小部分副本,成功后再更新更多的副本,最终完成所有副本的更新。滚动更新最大的好处就是零停机,保证了业务的连续性。
- 对本地代码进行一点改动
- 编译、打标签,并推送到本地仓库中
- echo 'Building to dits ...'
- tsc
-
- echo 'Docker building image ...'
- docker build --rm -t localhost:5000/gateapp:0.0.2 .
-
- echo 'Pushing ...'
- docker push localhost:5000/gateapp
-
- echo 'Docker list images ...'
- docker images
- 复制代码
这时候可以看到有0.0.1 和 0.0.2两个版本的镜像
-
在Dashboard中编辑Deployment,把
image: localhost:5000/gateapp:0.0.1
改成image: localhost:5000/gateapp:0.0.2
,点击更新 -
等待一段时间,再观察所有的Pod都被更新成0.0.2版本的了,是不是很方便!
反复执行上面的步骤,不难发现更新过程中Kubernets都做了什么:
- Deployment的镜像被更新为0.0.2版本
- 新创建了一个名称为gate-deployment-dev-594468997c的ReplicaSet副本集,镜像为0.0.2
- 新的ReplicaSet增加了一个Pod
- 旧的ReplicaSet减少了一个Pod
- 逐渐的新的ReplicaSet接管了所有旧ReplicaSet的Pod,滚动更新完成
-
自定义滚动更新行为
- strategy:
- rollingUpdate: # 滚动更新策略
- maxSurge: 10% # 数值越大 滚动更新时新创建的副本数量越多
- maxUnavailble: 10% # 数值越大 滚动更新时销毁的旧副本数量越多
- replicas: 2 # 运行的Pod副本数量
- 复制代码
- - maxSurge:此参数控制滚动更新中副本总数超过DESIRED的数量或最大比例,数值越大 滚动更新时新创建的副本数量越多
- - maxUnavailble:此参数控制滚动更新中,不可用的副本占DESIRED的最大数量或最大利弊,数值越大 滚动更新时销毁的旧副本数量越多
- 复制代码
那如果更新过程出错了怎么办?请接着往下看
健康检查
Kubernetes有很强大的自愈能力,默认的实现方式是重启发生故障的容器,此外用户可以使用Liveness、 Readiness的探测机制设置更为精细的健康检查,进而真正实现零停机部署、避免部署无效的镜像、更加安全的滚动升级
- 编辑之前的Deployment.yaml,例如:
- image: localhost:5000/gateapp:0.0.13
- readinessProbe: # 一种健康检查决定是否加入到service 对外服务
- httpGet:
- scheme: HTTP # 支持http https
- path: /healthy
- port: 80 # 与你的pod端口一致
- initialDelaySeconds: 10 # 容器启动多久后开始检查
- periodSecods: 5 # 几秒检查一次
- 复制代码
-
readinessProbe即是使用Readiness健康探测机制,当检查不通过时(例如接口返回的状态码不是200-400之间),Kubernetes就不会把容器添加到Service中供外界访问,观察Pod的状态为Not Ready。 initialDelaySeconds是决定容器启动多久后开始检查,通常要比启动时间再长一些;periodSecods是多久检查一次,连续3次探测失败后,Ready将变成不可用,Kubernets把这个Pod从Service中下线
-
LiveNess的配置项和Readiness的一样,不同之处在于探测失败后的行为。前者会重启容器,后者会设置Pod不可用,并从Service中下线。
-
LiveNess和Readiness的探测是独立使用的,二者没有依赖,可以单独使用也可以同时使用。一般情况下使用LiveNess判断容器是否需要重启实现自愈;Readiness判断容器是否已经准备好对外提供服务。
- 自定义健康检查的代码: 自定义/healthy get接口,比如数据库连接等等
- @Get('healthy')
- async checkHealthy(@Res() res: Response) {
- let isHealthy = false;
-
- // some code to check healthy begin
- isHealthy = true;
- // check end
-
- const data = {
- isHealthy,
- };
-
- debug('执行健康检查结果 %O', data);
-
- res.status(isHealthy ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR).json(data);
- }
- 复制代码
一起来学习Kubernetes
相信看完以上的文章,你也会认为Kubernetes真的非常强大,是非常值得学习的。笔者也是小白一个,从0开始学习的,如果你也想一起,可以加入我们的群。