Kubernetes (音 kubə'netis) 这个名字来自希腊语,意思是「舵手」或「领航员」,它是一个起源于 Google 的开源项目,允许自动化部署、管理和扩容容器化应用,它现在已成为容器编排的事实标准。
其实 Kubernetes 的简称 k8s 存在感更强,这个简称缘由是单词中间刚好是 8 个字母,这是一种数字缩写 (Numeronym) 方式。类似的如 internationalization
(国际化) 叫做 i18n
、 accessibility
叫做 a11y
等。以下如无特别原因均使用它的简称 k8s。
接下来几篇文章将分享我学习和使用它的一些经验,今天是第一篇,先了解 k8s 的架构、核心概念和基本用法。
首先需要安装 Kubernetes。有 3 个方法:
Docker Desktop for Mac
在 macOS 上最简单的方法就是安装「Docker Desktop for Mac」,并开启 Kubernetes 支持,可以通过延伸阅读链接 1 的地址下载它,开启 Kubernetes 支持的方法非常简单,需要 2 步:
Apply && Restart
重启 DockerEnable Kubernetes
等项打勾再点 Apply
,再点弹出窗口的「Install」项等待完成完成即可P.S 安装过程中如果有任何问题可以使用延伸阅读链接 2 的检查日志方法去做故障排错,根据日志输出再搜索解决方案。
通过 k8s-docker-desktop-for-mac
可以用我厂平台同学搞的 k8s-docker-desktop-for-mac 项目完成安装~
命令行安装
如果你没有好用的代理,也不想尝试:point_up_2:这个项目,兜底方案是手动安装。第一步先安装 Kubernetes 的命令行客户端 kubectl,再安装一个可以在本地跑起来的 Kubernetes 环境 Minikube、以及给 Minikube 使用的虚拟化引擎 HyperKit:
❯ brew cask install minikube ❯ brew install docker-machine-driver-hyperkit ❯ sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit # 文件路径可以不同,注意终端输出 ❯ sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit ❯ minikube start --vm-driver hyperkit --image-mirror-country cn # 使用国内镜像
Minikube 默认的虚拟化引擎是 VirtualBox,Hyperkit 是一个更快更轻量的替代。Minikube 自带了 Docker 引擎,所以我们需要重新配置客户端,让 docker 命令行与 Minikube 中的 Docker 进程通讯:
❯ eval $(minikube docker-env)
现在 k8s 就安装完成啦。我们进入正题~
典型 Kubernetes 集群包含一个 Master 和多个 Node,简单架构如下图所示 (未来 3 张图来源于延伸阅读链接 3):
Master 节点
Master 是集群的控制节点,它负责整个集群的管理和控制 (调度)。这个节点上运行着多个组件,核心的如下:
看一下 Mater 节点的组件架构效果图:
Pod 是什么很快就说到了哈~
Node
Node 是集群的工作节点,它提供 CPU、内存和存储等资源。它上面运行哪些容器应用由 Master 节点分配。这个节点上也运行着多个组件,核心的如下:
看一下 Node 节点的组件架构效果图:
这个图里面还标了一些插件,如 CoreDNS、Dashboard 以及没有替到的 Ingress Controller 等,之后还会再说,内容太多,我们先了解基础部分。
通过下面的命令可以看到集群节点情况:
❯ kubectl get nodes
NAME STATUS ROLES AGE VERSION
docker-desktop Ready master 2m v1.14.
在学习过程中有什么不懂得可以加我的
python学习交流扣扣qun,10667510
群里有不错的学习视频教程、开发工具与电子书籍。
与你分享python企业当下人才需求及怎么从零基础学习好python,和学习什么内容
6
当然,我们这个是单节点集群环境,只有一个 Master 节点,但肩负了 Node 节点的作用。
Pod 是 k8s 里面基本的部署调度单元,每个 Pod 可以由一个或多个容器组成,容器之间共享存储、网络等资源。Pod 这个词一眼看去是不是很懵?其实看一下它中文翻译「豆荚」的图就好理解了:
豌豆被豌豆荚「包」了起来,这就是一个 Pod。所以可以把单个 Pod 理解成是运行独立应用的「逻辑主机」—— 其中运行着一个或者多个紧密耦合的应用容器。
在一开始学习基础概念阶段我会用一些非常简单的例子帮助大家消化 k8s 的知识。首先创建一个单独的目录来存放示例所需的文件,实现一个用 Nginx 访问静态文件的服务:
❯ mkdir k8s-demo ❯ cd k8s-demo ❯ echo 'Hello Docker!
' > index.html
接着创建 Dockerfile 文件并构建镜像:
❯ cat Dockerfile FROM nginx:1.17.4-alpine COPY index.html /usr/share/nginx/html ❯ docker build -t k8s-demo:0.1 . ... # 省略输出 Successfully built 1cdb5b879af0 Successfully tagged k8s-demo:0.1
这样就在本地构建了一个叫做 k8s-demo
、标签 (版本) 为 0.1 的镜像。接着把它提交给 k8s 并部署,不过要注意,在 k8s 里面最小的部署单元不是容器而是 Pod,所以需要「转化」成 Pod 对象,再交由 k8s 创建。怎么做呢?
# 1. 创建一个定义文件,比如这里叫做pod.yaml,当然用JSON格式也可以,不过我觉得YAML的表达能力和可读性更强 ❯ cat pod.yaml apiVersion: v1 kind: Pod metadata: name: k8s-demo labels: app: k8s spec: containers: - name: k8s-demo image: k8s-demo:0.1 ports: - containerPort: 80 # 2. 使用「kubectl create -f YAML_FILE」创建Pod ❯ kubectl create -f pod.yaml pod/k8s-demo created ❯ kubectl get pods # 查看Pod状态 NAME READY STATUS RESTARTS AGE k8s-demo 1/1 Running 0 21s ❯ kubectl describe pods | grep Labels # 查看Pod标签 Labels: app=k8s
到这里,这个叫做 k8s-demo 的 Pod 就创建成功了,而且已经是运行状态了。在继续之前,非常有必要先说一下 k8s 的对象以及对象管理
Pod 是一种 k8s 中对象 (Object) 中的一种,k8s 用各种对象来表示对应实体在集群中的状态。上面说的 Pod 就是一种对象,之后我们还会介绍非常多的实体,如 ReplicaSet、Deployment、Job、Service、CronJob、ConfigMap、Volume...
这些对象有 3 个显著的特点:
仔细的回味下 pod.yaml 这个配置文件的键值内容:
k8s-demo
,使用刚才构建的 k8s-demo:0.1
作为镜像,容器可被访问的端口是 80前三个键 (apiVersion、kind 和 metadata) 是所有对象都有的,而所有表示物理资源的对象,其状态可能与用户期望的意图不同时才可能有 spec 键。
P.S 上述配置项相关理解基于延伸阅读链接 5 里面的「API 约定文档」而来,建议阅读。
经过这段时间学习 k8s,非常喜欢它的最核心的对象配置方法:「命令式对象配置」和「声明式对象配置」
命令式对象配置
前面的 kubectl create -f pod.yaml
就是命令式: kubectl
命令指定操作 (创建,替换等),需要接受至少一个文件名称。指定的文件必须包含对象的完整定义 (以 YAML 或 JSON 格式):
kubectl create -f nginx.yaml # 创建对象定义配置文件 kubectl delete -f nginx.yaml -f redis.yaml # 删除2个配置文件中定义的对象 kubectl replace -f nginx.yaml # 通过覆写实时配置更新配置文件中定义的对象
要不然是基于 YAML 文件执行添加 / 删除操作,否则就是直接修改 YAML 文件内容实现对 k8s 中对象的修改。
它和传统的、把全部配置都作为参数写在一个命令里面的方式相比,非常节省命令长度,一切相关的配置都在 YAML 文件中表达,另外这样写的好处还有:
声明式对象配置
通过声明式对象配置的用法,可以实现目录级别的对象管理,能自动检测实现对每个对象进行创建、更新和删除等操作:
kubectl apply -f configs/
前面已经了解了 Pod,但是要注意 Pod 是不能从外部直接访问的 (除非用 kubectl port-forward 等方案)。要把服务暴露出来给用户访问,需要创建一个服务 (Service)。Service 的作用主要是做反向代理 (Reverse Proxy) 和负载均衡 (LB),负责把请求分发给后面的 Pod。
首先要创建一个 Service 定义文件 svc.yaml:
❯ cat svc.yaml apiVersion: v1 kind: Service metadata: name: k8s-demo-svc spec: selector: app: k8s type: NodePort ports: - protocol: TCP port: 80 targetPort: 80
这个配置文件描述了如下内容:
k8s-demo-svc
app:k8s
在上面的 pod.yaml 里面指定了,所以这个服务会把流量发送给 k8s-demo
这个 Pod创建并查看服务:
❯ kubectl create -f svc.yaml # 创建服务 service/k8s-demo-svc created ❯ kubectl get svc k8s-demo-svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE k8s-demo-svc NodePort 10.100.68.4880:31404/TCP 6s
PORT 项下面有个 80:31404
,就是说访问本机 (127.0.0.1) 的 31404 端口 (端口号是 k8s 分配的,你在使用时很可能不是这个端口号) 会被转发到这个服务的 80 端口,所以现在在浏览器输入 http://127.0.0.1:31404/ 就可以看到「Hello Docker!」了。
假如现在有一个 Pod 正在提供线上的服务,设想出现如下问题时应该怎么办:
针对第一点,可以提前和运维打好招呼,按照预估提前多启动 N 个 Pod,结束后删掉,缺点是不能自动扩展造成资源浪费,给平台带来开发和运维成本;针对第二点,提前充分准备并演练好切换预案,运维 24 小时待命并及时关注上线服务报警,出现问题时快速切换节点。
这样的解决方案其实不算解决方案,因为的弊端很明显:纯靠人工!!!所以我们需要 k8s 这样的工具帮助我们实现 Pod 的自动扩展和「故障转移」。
在 k8s 资源对象中的 ReplicaSet (以下简称 RS,其实还有另外一种资源对象 Replication Controller,简称 RC,它已经被 ReplicaSet 取代) 就是做这件事的,它 保证在任意时间运行 Pod 的副本数量 ,能够保证 Pod 总是可用的。也就是说如果实际的 Pod 数量比指定的多就结束掉多余的,反之亦然。如果 Pod 失败、被删除或者挂掉后 ReplicaSet 会重新创建 Pod,所以即使只有一个 Pod,也应该使用 RS 来管理我们的 Pod。
另外 RS 还能实现非常有用的「滚动升级」,实现了零停机的前提下部署新版本,马上会详细说到。
实际上很少直接用 ReplicaSet 这个对象,一般用 Deployment 这个更加高层的资源对象代替。为什么呢?
RS 主要功能是确保 Pod 数量、健康度和滚动升级等,但 Deployment 除具备 RS 全部功能之外还有如下功能:
可以这样理解 Deployment、ReplicaSet 和 Pod 的关系:
一个 Deployment 拥有多个 Replica Set,而一个 Replica Set 拥有一个或多个 Pod
通过一个小例子完整的感受一下,先创建 deployment.yaml:
❯ cat deployment.yaml apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1 kind: Deployment metadata: name: k8s-demo-deployment spec: replicas: 10 template: metadata: labels: app: k8s spec: containers: - name: k8s-demo-pod image: k8s-demo:0.1 ports: - containerPort: 80 ❯ kubectl create -f deployment.yaml deployment.apps/k8s-demo-deployment created
这次 YAML 文件和前面的 Pod 的有个和不同的地方:
注意:创建 Deployment 的命令还是一样的 kubectl create -f XXX.yaml
,一切都在配置文件中指定。
看一下现在的副本集情况 (有 10 个 Pod 在运行):
❯ kubectl get rs # READY=10表示都可用 NAME DESIRED CURRENT READY AGE k8s-demo-deployment-7f6d84f56b 10 10 10 12s ❯ kubectl get deployments # 其实还可以直接看Deployment,AVAILABLE=10 NAME READY UP-TO-DATE AVAILABLE AGE k8s-demo-deployment 10/10 10 10 20s ❯ kubectl get pods |grep k8s-demo-deployment | wc -l # 可以看到Deployment维护了10个Pod 10
接着修改一下 k8s-demo 镜像:
❯ echo 'Hello Kubernetes!
' > index.html ❯ docker build -t k8s-demo:0.2 . ... Successfully built 068ab5dbcf44 Successfully tagged k8s-demo:0.2
接着要更新 deployment.yaml:
❯ cat deployment.yaml apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1 kind: Deployment metadata: name: k8s-demo-deployment spec: replicas: 10 minReadySeconds: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: k8s-demo spec: containers: - name: k8s-demo-pod image: k8s-demo:0.2 ports: - containerPort: 80
和之前的 deployment.yaml 相比,改动如下:
maxUnavailable: 1
指同时处于不可用状态的 Pod 不能超过一个, maxSurge: 1
指多余的 Pod 不能超过一个这样 k8s 就会逐个替换 Service 后面的 Pod 实现更新:
❯ kubectl apply -f deployment.yaml --record
这次多了一个 --record
参数,这会让 k8s 把这行命令记到发布历史中备查。执行完上述命令可以马上执行如下命令显示发布的实时状态 (晚了就看不到了):
❯ kubectl rollout status deployment k8s-demo-deployment ... # 省略 Waiting for deployment "k8s-demo-deployment" rollout to finish: 8 out of 10 new replicas have been updated... Waiting for rollout to finish: 1 old replicas are pending termination... ... # 省略 deployment "k8s-demo-deployment" successfully rolled out
更新过程会先添加新的副本集,再删除旧的,所以过程中如果看 kubectl get pods |grep k8s-demo-deployment | wc -l
值会大于 10。
还可以查看发布历史 (正是由于 --record):
❯ kubectl rollout history deployment k8s-demo-deployment deployment.extensions/k8s-demo-deployment REVISION CHANGE-CAUSE 12 kubectl apply --filename=deployment.yaml --record=true
现在你刷新页面 ( http://127.0.0.1:31404/)时可以看到文本已经成了「Hello Kubernetes!」
假设新版发布后发现有严重的 bug,需要马上回滚到上个版本,可以用下面命令完成:
❯ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1 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。