Py学习  »  Python

Python项目容器化实践(四) - Kubernetes基础篇

Python之美 • 4 年前 • 434 次点击  

前言

Kubernetes(音 kubə'netis)这个名字来自希腊语,意思是「舵手」或「领航员」,它是一个起源于Google的开源项目,允许自动化部署、管理和扩容容器化应用,它现在已成为容器编排的事实标准。

其实Kubernetes的简称k8s存在感更强,这个简称缘由是单词中间刚好是8个字母,这是一种数字缩写(Numeronym)方式。类似的如 internationalization(国际化)叫做 i18naccessibility叫做 a11y等。以下如无特别原因均使用它的简称k8s。

接下来几篇文章将分享我学习和使用它的一些经验,今天是第一篇,先了解k8s的架构、核心概念和基本用法。

在macOS上安装Kubernetes

首先需要安装Kubernetes。有3个方法:

Docker Desktop for Mac

在macOS上最简单的方法就是安装「Docker Desktop for Mac」,并开启Kubernetes支持,可以通过延伸阅读链接1的地址下载它,开启Kubernetes支持的方法非常简单,需要2步:

  1. 使用代理。众所周知,安装过程会请求一些会超时的网站页面,需要设置代理。右键点Docker图标选: Preferences -> Proxies,选择手动指定,输入你的代理地址,然后点 Apply&&Restart重启Docker

  2. 开启Kubernetes支持。重启后,右键点Docker图标选: Preferences -> Kubernetes Tab,给 EnableKubernetes等项打勾再点 Apply,再点弹出窗口的「Install」项等待完成完成即可

P.S 安装过程中如果有任何问题可以使用延伸阅读链接2的检查日志方法去做故障排错,根据日志输出再搜索解决方案。

通过k8s-docker-desktop-for-mac

可以用我厂平台同学搞的k8s-docker-desktop-for-mac项目完成安装~

命令行安装

如果你没有好用的代理,也不想尝试👆这个项目,兜底方案是手动安装。第一步先安装Kubernetes的命令行客户端kubectl,再安装一个可以在本地跑起来的 Kubernetes 环境Minikube、以及给Minikube使用的虚拟化引擎HyperKit:

  1. brew cask install minikube

  2. brew install docker-machine-driver-hyperkit

  3. sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit # 文件路径可以不同,注意终端输出

  4. sudo chmod u+s /usr/local/ bin/docker-machine-driver-hyperkit

  5. minikube start --vm-driver hyperkit --image-mirror-country cn # 使用国内镜像

Minikube默认的虚拟化引擎是VirtualBox,Hyperkit是一个更快更轻量的替代。Minikube自带了Docker引擎,所以我们需要重新配置客户端,让docker命令行与Minikube中的Docker进程通讯:

  1. eval $(minikube docker-env)

现在k8s就安装完成啦。我们进入正题~

Kubernetes架构

典型Kubernetes集群包含一个Master和多个Node,简单架构如下图所示(未来3张图来源于延伸阅读链接3):

Master节点

Master是集群的控制节点,它负责整个集群的管理和控制(调度)。这个节点上运行着多个组件,核心的如下:

  • API服务器(kube-apiserver)。API Server对外暴露了k8s API,提供HTTP REST服务。提供了认证、授权、访问控制、API注册和发现等机制

  • etcd。是一个高可用的键值对存储系统,被用作k8s的后端存储,所有集群配置数据都存储在里面,用于服务发现和集群管理,可以说是存了整个集群的状态

  • 控制管理器(kube-controller-manager)。Controller负责维护集群的状态,比如故障检测、自动扩展、滚动更新等

  • 调度器(kube-scheduler)。Scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上

看一下Mater节点的组件架构效果图:

Pod是什么很快就说到了哈~

Node

Node是集群的工作节点,它提供CPU、内存和存储等资源。它上面运行哪些容器应用由Master节点分配。这个节点上也运行着多个组件,核心的如下:

  • 节点代理(kubelet)。它和Master节点协作,实现Pod的创建、启动、监控、重启、销毁等集群管理工作

  • 转发代理(kube-proxy)。维护主机上的网络规则并执行连接转发,实现服务的负载均衡和反向代理

  • Docker。负责用于运行容器(或者说Pod)

  • fluentd。fluentd 是一个守护进程,它有助于提供集群层面日志 集群层面的日志。

看一下Node节点的组件架构效果图:


这个图里面还标了一些插件,如CoreDNS、Dashboard以及没有替到的Ingress Controller等,之后还会再说,内容太多,我们先了解基础部分。

通过下面的命令可以看到集群节点情况:

  1. kubectl get nodes

  2. NAME STATUS ROLES AGE VERSION

  3. docker-desktop Ready master 2m v1.14.6

当然,我们这个是单节点集群环境,只有一个Master节点,但肩负了Node节点的作用。

Pod

Pod是k8s里面基本的部署调度单元,每个Pod可以由一个或多个容器组成,容器之间共享存储、网络等资源。Pod这个词一眼看去是不是很懵?其实看一下它中文翻译「豆荚」的图就好理解了:

豌豆被豌豆荚「包」了起来,这就是一个Pod。所以可以把单个Pod理解成是运行独立应用的「逻辑主机」——其中运行着一个或者多个紧密耦合的应用容器。

在一开始学习基础概念阶段我会用一些非常简单的例子帮助大家消化k8s的知识。首先创建一个单独的目录来存放示例所需的文件,实现一个用Nginx访问静态文件的服务:

  1. mkdir k8s-demo

  2. cd k8s-demo

  3. echo '

    Hello Docker!

    ' > index.html

接着创建Dockerfile文件并构建镜像:

  1. cat Dockerfile

  2. FROM nginx:1.17.4-alpine

  3. COPY index.html /usr/share/nginx /html

  4. docker build -t k8s-demo:0.1 .

  5. ... # 省略输出

  6. Successfully built 1cdb5b879af0

  7. Successfully tagged k8s-demo:0.1

这样就在本地构建了一个叫做 k8s-demo、标签(版本)为0.1的镜像。接着把它提交给k8s并部署,不过要注意,在k8s里面最小的部署单元不是容器而是Pod,所以需要「转化」成Pod对象,再交由k8s创建。怎么做呢?

  1. # 1. 创建一个定义文件,比如这里叫做pod.yaml,当然用JSON格式也可以,不过我觉得YAML的表达能力和可读性更强

  2. cat pod .yaml

  3. apiVersion: v1

  4. kind: Pod

  5. metadata:

  6. name: k8s-demo

  7. labels:

  8. app: k8s

  9. spec:

  10. containers:

  11. - name: k8s-demo

  12. image: k8s-demo:0.1

  13. ports:

  14. - containerPort: 80

  15. # 2. 使用「kubectl create -f YAML_FILE」创建Pod

  16. kubectl create -f pod.yaml

  17. pod/k8s-demo created

  18. kubectl get pods # 查看Pod状态

  19. NAME READY STATUS RESTARTS AGE

  20. k8s-demo 1/1 Running 0 21s

  21. kubectl describe pods | grep Labels # 查看Pod标签

  22. Labels: app=k8s

到这里,这个叫做k8s-demo的Pod就创建成功了,而且已经是运行状态了。在继续之前,非常有必要先说一下k8s的对象以及对象管理

对象和对象管理

Pod是一种k8s中对象(Object)中的一种,k8s用各种对象来表示对应实体在集群中的状态。上面说的Pod就是一种对象,之后我们还会介绍非常多的实体,如ReplicaSet、Deployment、Job、Service、CronJob、ConfigMap、Volume...

这些对象有3个显著的特点:

  • 被持久化的存储进etcd

  • 通过对象配置的方式管理

  • 一旦创建对象,k8s将持续工作以确保对象按照其配置期望的那样存在

仔细的回味下pod.yaml这个配置文件的键值内容:

  • apiVersion。API版本号,这里的值是v1,但是这个版本号需要根据我们安装的k8s版本和资源对象类型变化,不是固定的

  • kind。描述对象的类型,本例中它就是一个「Pod」,不同的对象类型肯定不一样(如Deployment、Service、Job、Ingress等)

  • metadata。定义元数据,用于唯一识别对象的数据。常用的配置项如name(名字)、namespace(命令空间)、labels(标签)等

  • spec。规格声明,描述了某一个实体的期望状态,常用项如containers、storage、volumes、template等。在这里就是说用的容器名字为  k8s-demo,使用刚才构建的 k8s-demo:0.1作为镜像,容器可被访问的端口是80

前三个键(apiVersion、kind和metadata)是所有对象都有的,而所有表示物理资源的对象,其状态可能与用户期望的意图不同时才可能有spec键。

P.S 上述配置项相关理解基于延伸阅读链接5里面的「API约定文档」而来,建议阅读。

经过这段时间学习k8s,非常喜欢它的最核心的对象配置方法:「命令式对象配置」和「声明式对象配置」

命令式对象配置

前面的 kubectl create-f pod.yaml就是命令式: kubectl命令指定操作(创建,替换等),需要接受至少一个文件名称。指定的文件必须包含对象的完整定义(以YAML或JSON格式):

  1. kubectl create -f nginx.yaml # 创建对象定义配置文件

  2. kubectl delete -f nginx .yaml -f redis.yaml # 删除2个配置文件中定义的对象

  3. kubectl replace -f nginx.yaml # 通过覆写实时配置更新配置文件中定义的对象

要不然是基于YAML文件执行添加/删除操作,否则就是直接修改YAML文件内容实现对k8s中对象的修改。

它和传统的、把全部配置都作为参数写在一个命令里面的方式相比,非常节省命令长度,一切相关的配置都在YAML文件中表达,另外这样写的好处还有:

  • 方便把对象配置存储在源码控制系统(如Git)中,且方便对比修改

  • 表达能力极强,这样支持的配置项更丰富灵活,且能使用模板

声明式对象配置

通过声明式对象配置的用法,可以实现目录级别的对象管理,能自动检测实现对每个对象进行创建、更新和删除等操作:

  1. kubectl apply -f configs/

延伸阅读链接7中对于这部分概念讲的非常好,强烈建议阅读

Service(服务)

前面已经了解了Pod,但是要注意Pod是不能从外部直接访问的(除非用kubectl port-forward等方案)。要把服务暴露出来给用户访问,需要创建一个服务(Service)。Service的作用主要是做反向代理(Reverse Proxy)和负载均衡(LB),负责把请求分发给后面的Pod。

首先要创建一个Service定义文件svc.yaml:

  1. cat svc.yaml

  2. apiVersion: v1

  3. kind: Service

  4. metadata:

  5. name: k8s-demo-svc

  6. spec:

  7. selector:

  8. app: k8s

  9. type: NodePort

  10. ports:

  11. - protocol : TCP

  12. port: 80

  13. targetPort: 80

这个配置文件描述了如下内容:

  • 资源类型kind是Service

  • 服务名字 k8s-demo-svc

  • spec.selector指定请求会被发送给集群里的哪些Pod,一般是用标签选择, app:k8s在上面的pod.yaml里面指定了,所以这个服务会把流量发送给 k8s-demo这个Pod

  • spec.type指定服务暴露方式,这里是NodePort,Master会从由启动参数配置的范围(默认是30000-32767)中分配一个端口,然后每一个Node都会将这个端口代理到你的Service。。其他还有LoadBalancer、Ingress等。

  • spec.ports的设置表示访问节点的9376端口,会被转发到Pod的80端口

创建并查看服务:

  1. kubectl create -f svc.yaml # 创建服务

  2. service/k8s-demo-svc created

  3. kubectl get svc k8s-demo-svc

  4. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

  5. k8s-demo-svc NodePort 10.100.68.48 <none> 80 :31404/TCP 6s

PORT项下面有个 80:31404,就是说访问本机(127.0.0.1)的31404端口(端口号是k8s分配的,你在使用时很可能不是这个端口号)会被转发到这个服务的80端口,所以现在在浏览器输入http://127.0.0.1:31404/ 就可以看到「Hello Docker!」了。

ReplicaSet(副本)

假如现在有一个Pod正在提供线上的服务,设想出现如下问题时应该怎么办:

  • 某次广告投放或者运营活动引来大量用户,网站访问量突然暴增

  • 运行当前Pod的节点发生故障了,Pod不能正常提供服务了

针对第一点,可以提前和运维打好招呼,按照预估提前多启动N个Pod,结束后删掉,缺点是不能自动扩展造成资源浪费,给平台带来开发和运维成本;针对第二点,提前充分准备并演练好切换预案,运维24小时待命并及时关注上线服务报警,出现问题时快速切换节点。

这样的解决方案其实不算解决方案,因为的弊端很明显: 纯靠人工!!!所以我们需要k8s这样的工具帮助我们实现Pod的自动扩展和「故障转移」。

在k8s资源对象中的ReplicaSet(以下简称RS,其实还有另外一种资源对象Replication Controller,简称RC,它已经被ReplicaSet取代)就是做这件事的,它保证在任意时间运行Pod的副本数量,能够保证Pod总是可用的。也就是说如果实际的Pod数量比指定的多就结束掉多余的,反之亦然。如果Pod失败、被删除或者挂掉后ReplicaSet会重新创建Pod,所以即使只有一个Pod,也应该使用RS来管理我们的Pod。

另外RS还能实现非常有用的「滚动升级」,实现了零停机的前提下部署新版本,马上会详细说到。

Deployment

实际上很少直接用ReplicaSet这个对象,一般用Deployment这个更加高层的资源对象代替。为什么呢?

RS主要功能是确保Pod数量、健康度和滚动升级等,但Deployment除具备RS全部功能之外还有如下功能:

  • 事件和状态查看。可以查看Deployment的升级详细进度和状态

  • 回滚。当升级Pod的时候如果出现问题,可以使用回滚操作回滚到之前的任一版本

  • 版本记录。每一次对Deployment的操作,都能够保存下来,这也是保证可以回滚到任一版本的基础

  • 暂停和启动。对于每一次升级都能够随时暂停和启动

可以这样理解Deployment、ReplicaSet和Pod的关系:

一个Deployment拥有多个Replica Set,而一个Replica Set拥有一个或多个Pod

通过一个小例子完整的感受一下,先创建deployment.yaml:

  1. cat deployment.yaml

  2. apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1

  3. kind: Deployment

  4. metadata:

  5. name: k8s-demo-deployment

  6. spec:

  7. replicas: 10

  8. template:

  9. metadata:

  10. labels:

  11. app: k8s

  12. spec:

  13. containers:

  14. - name: k8s-demo-pod

  15. image: k8s-demo:0.1

  16. ports:

  17. - containerPort: 80

  18. kubectl create -f deployment.yaml

  19. deployment.apps/k8s-demo-deployment created

这次YAML文件和前面的Pod的有个和不同的地方:

  • apiVersion不同,且k8s版本不同值也不同

  • kind是Deployment

  • spec.replicas指定了这个deployment要有10个Pod

注意: 创建Deployment的命令还是一样的 kubectl create-f XXX.yaml,一切都在配置文件中指定。

看一下现在的副本集情况(有10个Pod在运行):

  1. kubectl get rs # READY=10表示都可用

  2. NAME DESIRED CURRENT READY AGE

  3. k8s-demo- deployment-7f6d84f56b 10 10 10 12s


  4. kubectl get deployments # 其实还可以直接看Deployment,AVAILABLE=10

  5. NAME READY UP-TO-DATE AVAILABLE AGE

  6. k8s-demo-deployment 10/10 10 10 20s


  7. kubectl get pods |grep k8s-demo-deployment | wc -l # 可以看到Deployment维护了10个Pod

  8. 10

接着修改一下k8s-demo镜像:

  1. echo '

    Hello Kubernetes!

    ' > index.html


  2. docker build -t k8s-demo:0.2 .

  3. ...

  4. Successfully built 068ab5dbcf44

  5. Successfully tagged k8s-demo:0.2

接着要更新 deployment.yaml:

  1. cat deployment.yaml

  2. apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1

  3. kind: Deployment

  4. metadata:

  5. name: k8s-demo-deployment

  6. spec:

  7. replicas: 10

  8. minReadySeconds: 3

  9. strategy:

  10. type: RollingUpdate

  11. rollingUpdate:

  12. maxUnavailable: 1

  13. maxSurge: 1

  14. template:

  15. metadata:

  16. labels:

  17. app: k8s-demo

  18. spec:

  19. containers:

  20. - name: k8s-demo-pod

  21. image: k8s-demo:0.2

  22. ports:

  23. - containerPort: 80

和之前的deployment.yaml相比,改动如下:

  • 增加了minReadySeconds,指在更新了一个Pod后,需要在它进入正常状态后3秒再更新下一个Pod

  • 增加strategy,升级策略是滚动升级(RollingUpdate), maxUnavailable: 1指同时处于不可用状态的Pod不能超过一个, maxSurge:1指多余的Pod不能超过一个

  • 修改了spec.containers.image(的标签),因为要升级镜像了

这样k8s就会逐个替换Service后面的Pod实现更新:

  1. kubectl apply -f deployment.yaml --record

这次多了一个 --record参数,这会让k8s把这行命令记到发布历史中备查。执行完上述命令可以马上执行如下命令显示发布的实时状态(晚了就看不到了):

  1. kubectl rollout status deployment k8s-demo-deployment

  2. ... # 省略

  3. Waiting for deployment "k8s-demo-deployment" rollout to finish: 8 out of 10 new replicas have been updated...

  4. Waiting for rollout to finish: 1 old replicas are pending termination...

  5. ... # 省略

  6. deployment "k8s-demo-deployment" successfully rolled out

更新过程会先添加新的副本集,再删除旧的,所以过程中如果看 kubectlgetpods|grep k8s-demo-deployment| wc-l值会大于10。

还可以查看发布历史(正是由于--record):

  1. kubectl rollout history deployment k8s-demo-deployment

  2. deployment.extensions/k8s-demo-deployment

  3. REVISION CHANGE-CAUSE

  4. 1 <none>

  5. 2 kubectl apply --filename=deployment.yaml --record= true

现在你刷新页面(http://127.0.0.1:31404/)时可以看到文本已经成了「Hello Kubernetes!」

假设新版发布后发现有严重的bug,需要马上回滚到上个版本,可以用下面命令完成:

  1. kubectl rollout undo deployment k8s-demo-deployment --to-revision=1

  2. deployment.extensions/k8s-demo-deployment rolled back

其中 --to-revision参数指定要回滚到那个位置,版本号1可以在上面 kubectl rollout history的输出列表中找到。在回滚结束之后,刷新浏览器可以看到内容又改回了「Hello Docker!」,这种滚动升级是不是非常好用?

Deployment如其名,更适合生产环境的部署。

P.S. 可以感受到前面用到的Service、Deployment和Pod的关系是解耦的: Service用于暴露给外部访问、Deployment用户部署方式、Pod包装容器,让它们关联起来的唯一纽带是配置文件中的 app:k8s ,对Deployment的修改会影响到Service和Pod。

后记

k8s中要说的内容实在太多了,所以我会分成多篇来说。

项目源码

本文提到的全部源码可以在mp找到。

延伸阅读

  1. https://www.docker.com/products/docker-desktop

  2. https://docs.docker.com/docker-for-mac/troubleshoot/#check-the-logs

  3. https://thenewstack.io/kubernetes-an-overview/

  4. https://1byte.io/developer-guide-to-docker-and-kubernetes/

  5. https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md

  6. https://www.qikqiak.com/k8s-book/docs/14.Kubernetes%E5%88%9D%E4%BD%93%E9%AA%8C.html

  7. http://www.k8smeetup.com/article/VyaHa$XRm


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/48068
 
434 次点击