阅读 1226

kubernetes学习笔记 (二):k8s初体验

本文采用本地k8s v1.10.3版本开发,如果还没有搭建可参照 kubernetes学习笔记 (一):搭建本地k8s开发环境进行搭建

搭建本地Docker镜像仓库

  1. docker pull docker.io/registry 拉取registry的镜像到本机
  2. docker run -d -p 5000:5000 --name=registry --restart=always --privileged=true --log-driver=none -v /home/data/registrydata:/tmp/registry registry 运行刚刚拉取的registry
  3. 在Docker for Mac中找到Docker -> Preferences -> Daemon,在insecure registries中填入自己刚刚搭建的本地仓库 http://localhost:5000

准备一个Docker镜像

  1. 随意使用什么语言写一个http接口,例如Nodejs:
    @Get('whoami')
    async whoAmI() {
        debug('whoAmI called with %O', {});

        return { serverType: Config.SERVER_TYPE };
    }
复制代码
  1. 编写对应的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

复制代码
  1. 编译、打标签,并推送到本地仓库中
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中的重要概念

  1. Cluster: Cluster是计算、存储和网络资源的集合,Kubernetes利用这些资源运行各种基于容器的应用
  2. Master: Master是Kubernetes的大脑,它的主要职责是调度,即决定将应用放在那里运行。
  3. Node: Node的职责是运行容器应用。Node由Master管理,Node负责监控并汇报容器的状态,同时根据Master的要求管理容器的生命周期。
  4. Pod: Pod是Kubernetes中最小工作单元。每个Pod包含一个或多个容器。Pod中的容器会作为一个整体被Master调度到一个Node上运行。
  5. Controller:Kubernetes通常不会直接创建Pod,而是通过Controller来管理Pod。Controller中定义了Pod的部署特性,比如有几个副本、在什么样的Node上运行等。为了满足不同的业务场景,Kubernetes提供了多种Controller,包括Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job等,后面会逐个学习这些Controller
  6. Service:Service定义外界访问一组特定Pod的方式。

创建Deployment部署应用

  1. 新建一个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的端口 需要自己代码中实现
复制代码
  1. 通过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

  1. 新创建一个yaml文件或者在上一个yaml文件中添加 --- 隔开
  2. 编辑以下内容:
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 监听的端口
复制代码
  1. kubectl apply -f xxx.yaml 或者在Dashboard中创建

  2. 观察Dashboard面板,会发现多了一个名字叫gate-svc-dev的服务,提供了监听了30000端口

  3. 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有哪些类型?

      1. ClusterIp:Service通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可以访问,这是默认的类型。
      2. NodePort: Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过<NodeIp>:<NodePort>访问Service
      3. LoadBalancer: cloud provider特有的对外提供服务,后续线上部署时会讲到

滚动更新

滚动更新是一次只更新一小部分副本,成功后再更新更多的副本,最终完成所有副本的更新。滚动更新最大的好处就是零停机,保证了业务的连续性。

  1. 对本地代码进行一点改动
  2. 编译、打标签,并推送到本地仓库中
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两个版本的镜像

  1. 在Dashboard中编辑Deployment,把image: localhost:5000/gateapp:0.0.1改成image: localhost:5000/gateapp:0.0.2,点击更新

  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,滚动更新完成
  3. 自定义滚动更新行为

    strategy:
        rollingUpdate: # 滚动更新策略
            maxSurge: 10% # 数值越大 滚动更新时新创建的副本数量越多
            maxUnavailble: 10% # 数值越大 滚动更新时销毁的旧副本数量越多
    replicas: 2 # 运行的Pod副本数量
复制代码
- maxSurge:此参数控制滚动更新中副本总数超过DESIRED的数量或最大比例,数值越大 滚动更新时新创建的副本数量越多
- maxUnavailble:此参数控制滚动更新中,不可用的副本占DESIRED的最大数量或最大利弊,数值越大 滚动更新时销毁的旧副本数量越多
复制代码

那如果更新过程出错了怎么办?请接着往下看

健康检查

Kubernetes有很强大的自愈能力,默认的实现方式是重启发生故障的容器,此外用户可以使用Liveness、 Readiness的探测机制设置更为精细的健康检查,进而真正实现零停机部署、避免部署无效的镜像、更加安全的滚动升级

  1. 编辑之前的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判断容器是否已经准备好对外提供服务。

  1. 自定义健康检查的代码: 自定义/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开始学习的,如果你也想一起,可以加入我们的群。