1 前言
我也是最近才学习的 K8s,所以把我学习的顺序、经验、踩过的坑、教程给记录下来,加深自己印象的同时也能帮助他人
2 命名空间
2.1 背景
为什么先讲 namespace,这是一个贯穿全文的概念,创建 Pod 、Service 、Controller 都会需要用到它,所以必须要先讲它。每讲到一个新的技术、新的知识时,我们都需要带着疑问去看待它,比如:
- 它是什么意思,它的概念是什么
- 它有什么作用,如果没有它,会怎么样,有了它之后,能解决什么问题
- 怎么使用它,它有哪些用法
- 笔者对它的了解是来源于哪里,有没有官方文档去介绍它。这样才能知道笔者讲解的知识是真实的,还是胡编乱造的
2.2 简介
Kubernetes 中命名空间(Namespace)是用来隔离 Kubernetes 集群内的不同资源对象的一种方式。每个 Kubernetes 对象都必须被分配到一个命名空间中,而且默认情况下,一个对象只能被同一个命名空间内的其他对象访问,Kubernetes 可以帮助用户在同一集群内部部署多个独立的应用程序,每个应用程序都在自己的命名空间内运行
命名空间(NameSpace)的核心作用就是 隔离资源,体现在以下几个地方:
- 同一命名空间下,资源是共享的。不同命名空间下,资源不共享,类似于
dev一个环境,prod一个环境 - 逻辑分组与环境划分,比如
dev、prod分组管理 - 避免命名冲突,资源名称只需在同一个命名空间内唯一。不同命名空间中可以存在同名的资源
- 精细化权限控制(
RBAC):可以为不同用户授予仅针对特定命名空间的读写权限,实现最小权限原则 - 轻量级多租户支持:在共享物理集群的场景下,命名空间是划分租户边界的基础。
试想一下,如果没有它,就存在以下的情况:
- 你部署的
Pod、Service、Controller等,别人都能直接看到,别人部署的你也能直接看到。 - 所有人部署的
Pod都在一起展示,哪个是你的?你还得费劲去找一下 - 当你想杀某个
Pod时,你可能会不小心把别人部署的Pod给杀了,这样就很危险
2.3 基本操作
2.3.1 创建命名空间
创建命名空间有 2 种方式。一种是:直接通过命令行
# 全写,创建一个名叫 testapp 的命名空间
kubectl create namespace testapp
# 简写
kubectl create ns testapp
另一种是:使用 yml 文件来创建命名空间,比如下面的脚本就是创建一个命名空间
apiVersion: v1
kind: Namespace
metadata:
# 命名空间的名字
name: ems

2.3.2 列出所有命名空间
# 全写
kubectl get namespaces
# 简写
kubectl get ns
# 使用插件
kubens
注意:kubens 是一个插件,需要下载

如上图所示,绿色高亮显示的是当前正在使用的命名空间
default 是默认的命名空间,是 k8s 自带的。
2.3.3 查看命名空间的详细信息
# 全写,查看命名空间详情 your_namespace 为你自己的命名空间名称
kubectl describe namespaces your_namespace
# 简写
kubectl describe ns your_namespace
2.3.4 删除命名空间
# 全写
kubectl delete namespaces your_namespace
# 简写
kubectl delete ns your_namespace
2.3.5 切换命名空间
# 切换命名空间
kubens ems(命名空间的名字)

切换命名空间后,再列出所有命名空间,可以看到,绿色高亮的已经变了,说明 ems 才是当前正在使用的命名空间
2.3.6 创建 Pod 时指定命名空间
# 部署应用到指定的命名空间
kubectl apply -f app.yml --namespace testapp
也可以在创建 Pod 的脚本中指定命名空间

2.3.6 查询指定命名空间下的 Pod
# 查询指定命名空间下的Pod
kubectl get pod --namespace kube-system
# 或者
kubectl get pod -n kube-system
2.4 说明
下面是一些常见的可以跨命名空间的资源对象
Node:节点(服务器),是不受命名空间约束的NamespaceClusterRoleClusterRoleBindingCustomResourceDefinition
下面是一些不能跨命名空间的资源对象
PodReplicaSetDeploymentServiceConfigMapSecretIngressPersistentVolumePersistenVolumeClaimRoleRoleBindingServiceAccount
2.5 官方文档
K8s 官方文档中有 2 个地方介绍命名空间,官方文档地址如下:
https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/namespaces/
https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/namespaces/
第 1 个介绍出现在官方文档中的 概念 → 概述 → 使用 Kubernetes 对象 → 命名空间(Namespaces)

这一章节主要介绍 命名空间 的概念
第 2 个出现在:任务(Tasks)→ 管理集群 → 使用命名空间共享集群

这一章节,主要介绍怎么怎么命名空间,比如如何创建、查看、删除等
需要额外注意的是:

k8s 有 4 个默认的命名空间
3 pod
3.1 简介
K8s 官网:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/#working-with-pods
Pod 是可以在 k8s 中创建和管理的、最小的可部署的计算单元
Pod 是一组(一个或多个容器),这些容器共享存储、网络、以及怎样运行这些容器的声明。这些容器在共享的上下文中运行。
Pod 的核心作用:
- 统一调度单位:
k8s不直接调度单个容器,而是调度Pod。 - 共享网络命名空间:
Pod内所有容器共享同一个IP地址、端口空间和路由表。容器之间可通过localhost直接通信,无需跨网络 - 共享存储卷:通过定义
Volumes,多个容器可挂载同一份存储 - 统一生命周期管理:
Pod作为一个整体被创建、健康检查、重启、扩缩容、销毁
3.2 Pod 基本操作
3.2.1 查看 Pod
1 查看有哪些 Pod
# 有三种写法,每种效果都一样
kubectl get pod
kubectl get pods
kubectl get po
这 3 个命令是一样的效果

上图中,表示在默认的命名空间中没有 Pod
2 查询指定命名空间下的 Pod
# 标准语法为:
kubectl get pod -n 命名空间名称
# 实战演示
kubectl get pod -n kube-system
-n:表示要查指定命名空间
kube-system :表示 k8s 自带的系统命名空间

3 查询所有命名空间下的 Pod
kubectl get pod -A

4 查看所有 Pod 的详细信息
# 查看默认命名空间下的pod的详细信息
kubectl get pod -o wide
# 查看系统命名空间下的pod信息
kubectl get pod -o wide -n kube-system
#查看所有命名空间下的所有pod的详细信息
kubectl get pod -o wide -A

ip:表示容器的 IP
5 查看指定 pod 的详细信息
# 查看指定pod的详细信息,比如pod名字是nginx
kubectl describe pod nginx


给大家解析一下关键信息
Name: nginx,表示 pod 的名称是 nginx
Namespaces: default,表示命名空间的名称是 default
IP:10.244.2.5,表示容器内的 IP
最后的 Events 表示事件,这个比较重要,因为很多时候排查问题需要用到它

那 Events 这几行信息到底是什么意思呢?
把 default 命名空间下的 pod: nginx,调度到 k8s-n3 节点上运行
拉取镜像 nginx:1.19
成功拉取镜像 nginx:1.19,花了 19s 的时间
创建 nginx 容器
启动 nginx 容器
6 实时监控 pod 的状态
kubectl get pod -w
-w:表示监控的意思

7 Pod 相位状态
Pending:Pod创建过程中,但它尚未被调度完成Running:Pod中所有容器都被创建成功Completed或successed:Pod中所有容器都已经成功终止,并不会被重启Failed:Pod中的容器至少有一个容器退出是非 0 状态Unknow:无法正常获取Pod对象的状态信息
3.2.2 创建 Pod
官网参考地址:https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1
下面写一个最简单的 pod ,写在了配置文件中,配置文件名字叫 nginx-pod.yml
apiVersion: v1 #代表使用的 api 版本
kind: Pod #代表创建类型
metadata: #元数据信息,指定 pod 名称以及 namespace
name: nginx #pod 的名称
spec: # 对Pod预期行为的规约,可以理解成描述容器的
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
创建 pod 的命令:
kubectl create -f nginx-pod.yml
kubectl apply -f nginx-pod.yml
注意:create 仅仅是不存在时创建,如果已经存在则报错。apply 不存在时创建,存在时更新配置
官方文档中对 pod 也做了介绍

metadata:对象的元数据信息,表示对对象的描述,前面的脚本中,对象是Pod,那metadata就是对Pod的描述,比如这个Pod的名字叫什么、命名空间是什么、标签是什么spec:是规约,是对Pod预期行为的规约。是用来描述容器信息的,比如用什么镜像、数据卷是什么、端口是什么
思考问题比较有深度的同学可能会想到,metadata 除了可以描述 Pod 名称、命名空间、标签,还能描述什么呢?

点进 ObjectMeta

上图发现,还有 generateName,但是这个东西几乎用不上,所以大家了解一下即可

spec 下面有哪些属性可以设置,也可以点击 PodSpec

上图可以看到,spec 下面有很多属性

常用的存储卷、节点选择、节点名称等,都是 spec 下的属性。这些属性有什么作用,等后面再讲。这里先对 Pod 的 metadata 和 spec 混个脸熟
3.2.3 删除 Pod
删除指定 pod ,比如 pod 名字是 nginx
kubectl delete pod nginx
# 删除某命名空间下的 Pod
kubectl delete pod <pod名称> -n <命名空间名称>
也可以根据配置文件来删
kubectl delete -f nginx-pod.yml
由于 Pod 是隶属于控制器下,控制器会监控 Pod 的健康值,出现问题会重启几次失败后删除重新创建,所以会出现删除后又出现的情况
3.2.4 进入 Pod 容器中
比如 Pod 名字叫 nginx ,怎么进入 Pod 呢
kubectl exec -it nginx (pod名称) -- bash
# 进入指定 pod 中指定容器
kubectl exec -it pod名称 -c 容器名称 -- bash
# 比如进入nginx的pod中的nginx的容器
kubectl exec -it nginx -c nginx -- bash
3.2.5 查看 Pod 日志
# 查看pod中第一个容器日志
kubectl logs -f nginx (pod名称)
# 查看pod中指定容器的日志
kubectl logs -f pod名称 -c 容器名称
3.2.6 查看 Pod 描述信息
kubectl describe pod nginx(pod名称)
# 查看某命名空间下的 Pod 详情
kubectl describe pod nginx(pod名称) -n your_namespace

3.3 Pod 运行多个容器
3.3.1 创建 Pod
apiVersion: v1
kind: Pod
metadata:
name: mypod
labels:
app: mypod
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
- name: redis
image: redis:5.0.10
ports:
- containerPort: 6379
文件名叫 mypod.yml
创建 pod:
kubectl apply -f mypod.yml
查看 pod
kubectl get pod

如上图所示,ready 的值为 2 ,表示 Pod 有 2 个容器
3.3.2 查看 pod 日志
# 查看pod中第一个容器的日志
kubectl logs -f mypod

查看 pod 中指定容器的日志,比如查看 redis 的日志
kubectl logs -f mypod -c redis

3.3.3 进入容器
# 默认进入pod中第一个容器
kubectl exec -it mypod -- bash
进入 Pod 中指定容器,比如进入 redis 容器
kubectl exec -it mypod -c redis - bash

3.4 Pod 的 Labels 标签
标签(Labels)是附加到 Kubernetes 对象(比如 Pod)上的键值对。标签旨在用于指定对用户有意义且相关的对象的标识属性
标签的作用:就是用来给 k8s 中对象起别名,有了别名就可以过滤和筛选。
下面创建一个 Pod,脚本为:
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
- name: redis
image: redis:5.0.10
ports:
- containerPort: 6379
下图中可以看到,执行 kubectl get pods,是看不到标签的

3.4.1 查看标签
kubectl get pods --show-labels

3.4.2 添加标签
kubectl label pod myapp env=dev
kubectl label :固定写法,标识打标签
pod :给谁打标签呢?给 pod 打标签
myapp :给哪个 pod 打标签呢?名字叫 myapp 的 pod
env=dev :标签的键值对

3.4.3 覆盖标签
比如标签写错了,写成了 env=dev,但是实际上想打的标签是 env=test
kubectl label --overwrite pod myapp env=test
--overwrite : 表示覆盖
3.4.4 删除标签
kubectl label pod myapp env-
- 号代表删除标签
env - : 表示删除 key 为 env 的键值对
3.4.5 根据标签筛选
# 筛选出标签为env=test的pod
kubectl get pod -l env=test
# 筛选出所有带env的pod
kubectl get pod -l env
# 筛选出key不为env的pod
kubectl get pod -l '!env'
# 筛选出所有key为env,值为test或prod的pod
kubectl get pod -l 'env in (test,prod)'
# 筛选出所有key为env,值不为test和prod的pod
kubectl get pod -l 'env notin (test,prod)'
3.5 Pod 的生命周期
参考官网:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/
Pod 本身不具有自愈能力,挂了就是挂了,不会自动重启。只是在控制器的管理下,才具有更高级的能力

3.6 容器特性
3.6.1 容器的生命周期
一旦调度器将 Pod 分派给某个节点,kubelet 就通过容器运行时开始为 Pod 创建容器,容器的状态有 3 种:Waiting(等待)、Running(运行中)和 Terminated(已终止)
要检查 Pod 中容器的状态,可以使用 kubectl describe pod 。其输出中包含 Pod 中每个容器的状态
Waiting(等待):处于 Waiting 状态的容器仍在运行它完成启动所需要的操作,比如从某个镜像仓库拉取镜像
Running(运行中):表明容器正在执行状态并且没有问题发生,如果配置了 postStart 回调,那么该回调已经执行且已完成
Terminated(已终止):处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行
3.6.2 容器生命周期回调/事件/钩子
PostStart:这个回调在容器被创建之后立即被执行。但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。没有参数传递给处理程序
PreStop:在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等)而被终止之前,此回调会被调用。如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。没有参数传递给处理程序
下面举个例子

创建 pod 后进入 nginx 容器,发现根目录下有个 start.txt 文件

删除容器
kubectl delete -f lifecycle
会睡 30s,容器才会真的删除

上图中这 2 行展示出来,等了 30s
3.6.3 容器重启策略
restartPolicy 表示重启策略,它跟 containers 是同级的
apiVersion: v1
kind: Pod
metadata:
name: mypod
labels:
name: mypod
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
容器的重启策略有 3 种:
Always:总是重启
OnFailure:容器异常退出状态非 0 重启
Never:永不重启
下图演示 Always:已经启动了一个 Pod,进入 Pod,关掉 nginx 容器,在执行 nginx -s stop 令之前,Pod 的状态还是 Running

执行 nginx -s stop 之后,Pod 停止了,但又立马重启了

3.6.4 自定义容器启动命令
自定义容器的启动命令:就是修改容器默认的启动命令
和 Docker 容器一样,k8s 中容器也可以通过 command、args 用来修改容器启动默认执行命令以及传递相关参数。但一般推荐使用 command 修改启动命令,使用 args 为启动命令传递参数
command:表示指令
args:表示参数

3.6.5 容器探针
定义:容器探针 就是用来定期对容器进行健康检查的

探针参数
initialDelaySeconds:5 # 初始化时间5s,表示容器容器5s之后才开始第一次探测
periodSeconds: 4 # 检测时间间隔4s,隔多长时间进行一次探针
timeoutSeconds: 1 # 默认检测超时时间为1s,一旦超过几秒之后,超时了,就认为失败
failureThreshold: 3 # 默认失败次数为3次,达到3次后重启pod
successThreshold: 1 # 默认成功次数为1次,1次检测成功代表成功

容器启动后睡 7s,
initialDelaySeconds: 5,表示初始探针为 5s,也就是容器启动 5s 之后去检测一次。检测时还处于睡眠状态,肯定检测失败
periodSeconds: 5,表示每 4s 检测一下。前面容器启动 5s 后检测一次,现在每 4s 又检测一下,那 4s 过后,相当于第 9s 了,此时容器苏醒,正常运行了,nginx.pid 文件已经存在了
使用 tcpSocket,如果 tcp 检测到了 80 端口,表示 nginx 容器已经启动成功了,没有检测到 80 端口,说明 nginx 没启动成功,检测失败,如下图所示:

使用 httpGet,也就是去发送一个 http 请求,如果给我一个 200 之类的响应,则我认为是容器启动成功了

nginx 有一个静态页面,访问 80 端口,路径是 index.html,如果能拿到结果,则认为这个 http 是请求成功的,拿不到说明这个 http 请求是失败的
3.6.6 资源限制
容器的资源限制,就是对容器的内存、CPU 进行资源限制。超过最大资源之后,容器会被 kill,oom 错误。如果不限制,容器是可以不限制地使用当前节点(服务器)的内存和 CPU 的
对内存的请求和限制:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
resources:
requests:
memory: 100M
limits:
memory: 200M
restartPolicy: Always
上面是一个资源限制的脚本:
requests:表示请求,申请资源是基础资源,memory: "100Mi"表示内存 100M,实际使用内存可以超过 100M,也可以少于 100M
limits:表示最大限制,memory: "200Mi",最大内存不能超过 200M,可以等于 200M
怎么查看 pod 当前使用了多少资源呢?
kubectl top pod nginx(pod名称)

内存请求和资源限制的目的:
可以有效利用集群节点上可用的内存资源。将 Pod 的内存请求保持在较低水平,可以更好地安排 Pod 调度。通过让内存限制大于内存请求,可以完成两件事:
Pod 可以进行一些突发活动,从而更好地利用可用内存
Pod 在突发活动期间,可使用的内存被限制为合理的数量
对 CPU 的请求和限制:
为容器之前 CPU 请求
apiVersion: v1
kind: Pod
metadata:
name: nginx-memory-demo
spec:
containers:
- name: nginx-memory-demo
image: nginx:1.19
ports:
- containerPort: 80
resources:
requests:
cpu: 1m
limits:
cpu: 4m
restartPolicy: Always
CPU 资源以 CPU 单位度量。小数值是可以使用的,一个请求 0.5CPU 的容器保证会获取请求 1 个 CPU 的容器的一半。可以使用后缀 m 表示毫。例如 100m CPU、100 milliCPU 和 0.1CPU 都相同。CPU 请求只能使用绝对数量,而不是相对数量。0.1 在单核、双核或 48 核计算机上的 CPU 数量值是一样的
3.7 Pod 中 init 容器
官网地址:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/
init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本
init 容器与普通的容器非常像,除了以下特点:
1、它们总是运行到完成。如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。然而,如果 Pod 对应的 restartPolicy 值为 never,并且 pod 的 init 容器失败,则 kubernetes 会将整个 Pod 的状态设置为失败
2、每个都必须在下一个启动之前成功完成
3、同时 Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe,因为它们必须在 Pod 就绪之前运行完成
4、如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。每个 Init 容器必须运行成功,下一个才能运行。当所有的 Init 容器运行完成时,Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行
5、Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。然而,Init 容器对资源请求和限制的处理稍有不同
3.8 节点亲和性分配 pod
官网地址:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node.html
可以约束 Pod 以便限制其只能在特定的节点上运行,或优先在特定的节点上运行。例如:将两个不同的服务且有大量通信的 `Pod 放在同一可用区
3.8.1 给节点加标签
# 获取所有集群中所有节点
kubectl get nodes

# 查询节点的标签
kunectl get nodes --show-labels

上图中是 k8s 自动给节点打的标签(默认标签)
# 给节点添加标签,比如打标签disk=ssd
kubectl label nodes k8s-n2(节点名) disk=ssd

3.8.2 选择节点标签指派 Pod
nodeSelector :节点选择
apiVersion: v1
kind: Pod
metadata:
name: nginx-memory-demo
spec:
containers:
- name: nginx-memory-demo
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
nodeSelector: # 明确指定当前pod指派到哪个标签对应节点上
disk: ssd # 将pod指派到标签为disk=ssd的标签上
如果有多个节点都带有 disk=ssd 的标签呢?则会先找到所有带有 disk=ssd 标签的节点,然后将 pod 随机分配到其中一个节点里
如果没有节点带有 disk=ssd 标签,或者有节点带有 disk 标签,但是值都不为 ssd ,怎么办?则 Pod 一直处于 Pending 状态,无法调度,因为找不到对应节点

3.8.3 指派 Pod 到指定节点
如果我就是只想把 Pod 指派到某个节点上,怎么做?使用 nodeName
apiVersion: v1
kind: Pod
metadata:
name: nginx-memory-demo
spec:
containers:
- name: nginx-memory-demo
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
# 根据节点名称指派Pod到指定节点
nodeName: k8s-n2
同样,找不到节点名的时候,Pod 会一直处于 Pending 状态
3.8.4 根据亲和性和反亲和性指派 Pod 到指定节点
官网地址:http://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/assign-pod-node.html
使用 亲和性 和 反亲和性 的好处:
- 亲和性、反亲和性语言的表达能力更强。
nodeSelect只能选择拥有所有指定标签的节点。亲和性、反亲和性可以为你提供选择逻辑的更强控制能力 - 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该
Pod - 你可以使用节点上(或其他拓扑中)运行的其他
Pod的标签来实施调度约束,而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些Pod可以被放置在一起
亲和性由两种类型的亲和性组成:
- 节点亲和性,功能类似于
nodeSelector字段,使你可以根据节点上的标签来约束Pod可以调度上哪些节点上 Pod间亲和性/反亲和性,运行根据其他Pod的标签来约束Pod
节点亲和性有 2 种:
requiredDuringSchedulingIgnoredDuringExecution:调度器只有在规则被满足的时候才能执行调度。此功能类似于nodeSelector。required就是必须的意思preferredDuringSchedulingIgnoredDuringExecution:调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该Pod。preferred就是喜欢、尝试的意思
注意:IgnoredDuringExecution 意味着如果节点标签在 k8s 上调度该 Pod 后发生了变更,Pod 将继续运行
3.8.5 节点亲和性
使用 Pod 规约中的 .spec.affinity.nodeAffinity 字段来设置 节点亲和性。
affinity :亲和性的意思
nodeAffinity :节点亲和性的意思
apiVersion: v1
kind: Pod
metadata:
name: nginx
# 规约
spec:
# 亲和性
affinity:
# 节点亲和性
nodeAffinity:
# 必须满足。节点必须包含一个键名为ssd的标签,且该标签的值必须为fast或superfast
requiredDuringSchedulingIgnoredDuringExecution:
# 节点选择
nodeSelectorTerms:
# 匹配表达式
- matchExpressions:
# 匹配什么样的标签呢呢?key为ssd
- key: ssd
operator: In
values:
- fast
- superfast
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
上面这个示例中,所应用的规则如下:节点必须包含一个名为 ssd 的键,并且该标签的取值必须为 fast 或 superfast
注意:可以使用 In 、NotIn 、Exists 、DoesNotExist 、Gt 、Lt 之一作为操作符。NotIn 、DoesNotExist 可用来实现节点反亲和性行为
requiredDuringSchedulingIgnoredDuringExecution 是强制的,required 是必须的意思,如果满足不了怎么办?
比如没有哪个节点的标签为 fast 或 superfast,找不到这种节点,怎么办?Pod 会一直处于 Pending 状态

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
weight: 2
- preference:
matchExpressions:
- key: disk
operator: In
values:
- machinery
weight: 90
preferredDuringSchedulingIgnoredDuringExecution 中 preferred 是偏好、尝试的意思。也就说找不到节点也没关系,最后 Pod 也能分配到节点上进行调度
weight 是权重,取值范围是 1-100 。当满足多个条件时,Pod 分配到哪个节点上呢?看哪个权重最高,就分配到哪个节点
3.8.6 Pod 间亲和性
官方文档:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
与节点亲和性类似,Pod 的亲和性与反亲和性也有 2 种类型:
requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution
例如:可以使用 requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器,将 2 个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁
要使用 Pod 间亲和性,可以使用 Pod 规约中的 spec.affinity.podAffinity 字段。对应 Pod 间反亲和性,可以使用 Pod 规约中的 spec.affinity.podAntiAffinity
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis:5.1.10
ports:
- containerPort: 6379
restartPolicy: Always
# 亲和性
affinity:
# Pod间亲和性
podAffinity:
# 必须满足
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: bj
# 标签选择器,Pod怎么知道跟哪个Pod要亲和、要部署在一起呢?就是通过Pod的标签
labelSelector:
# 匹配表达式
matchExpressions:
# Pod标签的key
- key: app
# 操作符
operator: In
# Pod标签的值是哪些
values:
- nginx
topologyKey :拓扑域参数,值是节点的标签,比如集群中有 20 个节点( 20 台服务器),20 个节点上都部署了 nginx ,其中 10 个节点在北京(节点标签是 bj=bj ),10 个在杭州(节点标签是 hz=hz )。但是我想把新的 Pod 部署到北京的节点上,而不是杭州的节点上,因为新的 Pod 需要频繁跟北京节点上的 Pod 通信。所以 bj 就是北京节点的标签,表示我选择北京这个域
为什么 topologyKey 的值是节点的标签?

上图是官方文档中的一段话,总结就是:Pod 会继承节点的拓扑标签( topology.kubernetes.io/zone 和 topolopy.kubernetes.io/region)
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
restartPolicy: Always
# 亲和性
affinity:
# Pod间亲和性
podAffinity:
# 尝试
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
topologyKey: bj
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
weight: 10
- podAffinityTerm:
topologyKey: bj
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- logs
weight: 90
也是去北京域的 10 台节点上去部署,具体去哪个节点上部署呢?如果多个节点上都有标签为 app=nginx 或 app=logs 的 Pod ,则根据权重来调度
Pod 规约中的 .affinity.podAffinity 来表示 亲和性,affinity.podAntiAffinity 表示 反亲和性
3.8.7 污点容忍度
参考官网:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/
3.8.8 Pod 拓扑分布约束
参考官网:http://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/topology-spread-constraints
4 Controller 控制器
4.1 Controller 控制器
4.1.1 什么是 Controller
参考官网:http://kubernetes.p2hp.com/docs/concepts/architecture/controller.html
Kubernetes 通常不会直接创建 Pod,而是通过 Controller 来管理 Pod。Controller 中定义了 Pod 的部署特性,比如有几个副本、在什么样的 Node 上运行等。通俗的说可以认为 Controller 就是用来管理 Pod 的。核心作用就是:通过监控集群的公共状态,并致力于将当前状态转变为期望的状态
4.1.2 常见的 Controller 控制器
Deployment:是最常用的 Controller,可以管理 Pod 的多个副本,并确保 Pod 按照期望的状态运行。ReplicaSet 以前也是一种 Controller,被合并到 Deployment 中了
Daemonset:是守护的意思,用于每个 Node 最多只运行一个 Pod 副本的场景。Daemonset 通常用于运行 daemon
Satefuleset:能保证 Pod 的每个副本在整个生命周期中名称是不变的,而其它 Controller 不提供这个功能。当某个 Pod 发生故障需要删除并重新启动时,Pod 的名称会发生变化,同时 StatefulSet 会保证副本按照固定的顺序启动、更新或删除
Job:用于运行结束就删除的应用,而其它 Controller 中的 Pod 通常是长期持续运行
4.1.3 Controller 如何管理 Pod
Controller 通过 label(标签)关联 Pods

4.2 Deployment
官网地址:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/
作用:官网原话:你负责描述 Deployment 中的目标状态,而 Deployment 控制器(Controller)以受控速率更改实际状态,使其变为期望状态 。
这句话听着有点蒙,你只需要知道 Deployment 作为控制器的一种,自然也是用来管理 Pod 的。
什么叫 描述目标状态,更改实际状态,使其变成期望状态呢?
- 描述目标状态:比如我希望
Pod有3个副本 - 实际状态:但是现在只有一个
Pod - 变成期望状态:
Deployment会自动创建2个Pod,这样就有3个副本,从而达到了期望状态
下面是一个创建 Deployment 的脚本
apiVersion: apps/v1
# 类型,已经不再是Pod了
kind: Deployment
# 控制器的元数据
metadata:
# 控制器的名字
name: nginx-deployment
# 控制器的标签,必须和要管理的Pod的标签保持一致,不然创建控制器会报错
labels:
app: nginx
# 规约,针对控制器的规约,而不是Pod的规约
spec:
# 副本数量,也就是说通过这个控制器创建的Pod,默认有几个副本。值为3,表示一下子就会创建3个Pod
replicas: 3
# 选择器,定义Deployment如何查找要管理的Pod,也就是定义Deployment要控制哪些带有哪些标签的Pod
selector:
matchLabels:
app: nginx
# 控制器的模板,描述控制器所管理的Pod
template:
# pod的元数据
metadata:
# Pod的名字
name: nginx
# Pod的标签
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:11.14.2
imagePullPolicy: IfNotPresent
restartPolicy: Always
注意:Deployment 的标签 = Pod 的标签 = selector 的标签
4.2.1 创建 deployment
创建控制器的命令跟创建 Pod 的命令一模一样
# 创建控制器
kubectl apply -f nginx-deployment.yml
4.2.2 查看 deployment
创建后怎么查看控制器呢
# 查看控制器
kubectl get deployment
或者
kubectl get deployments
# 只想查看某一个控制器
kubectl get deployment nginx-deployment(deployment的名称)

Name :deployment 的名称
Ready:显示应用程序的可用的副本数,显示模式是”就绪个数 / 期望个数“
UP-TO-DATE :显示为了达到期望状态已经更新的副本数
AVAILABLE:显示应用可供用户使用的副本数
AGE :显示应用程序运行的时间
查看控制器的标签
# 查看控制器的标签
kubectl get deployment --show-labels

如何查看控制器创建的 Pod 呢?
# 查看控制器创建的Pod
kubectl get pod
# 查看pod的标签
kubectl get pod --show-labels

上图可以看到,Pod 的名称是 nginx-deployment-6f4f7cc4d8-nfntm ,其中 nginx-deployment 是控制器的名称,6f4f7cc4d8-nfntm 是自动生成的唯一标识。
如何查看 Deployment 创建的副本(ReplicaSet)呢 ?ReplicaSet 简称 rs
# 查看 Deployment 创建的副本
kubectl get rs
官网介绍了其输出:

重点要记住 ReplicaSet 的名字,名字的组成是:[Deploymentm 名称]-[哈希]
而 Pod 的名称也是有规律的,名字的组成是:[ReplicaSet]-[随机 5 字符后缀]
4.2.3 更新 Deployment
官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/#pod-template-hash-label
为什么要更新 Deployment ?比如模板的标签、容器镜像需要被更新时,就需要更新 Deployment

官方文档中给了更严格的定义:仅当 Deployment Pod 模板(即 .spec.template)发生改变时,才会触发 Deployment 上线
下面回忆下创建 Deployment 的脚本
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template: #从这里开始,一旦有改变,就会触发 Deployment 上线
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:11.14.2
imagePullPolicy: IfNotPresent
restartPolicy: Always
比如我想修改镜像名字,那我得修改脚本,然后删除 Deployment ,然后重新创建,看着有点麻烦,有没有简单一点的办法呢?还真有

官方文档中给了 2 个命令来更新 Deployment 中 Pod 的镜像。我觉得第 2 个命令更容易理解,更容易记住
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
set image:修改镜像deployment/nginx-deployment:修改谁的镜像?修改控制器中的nxginx-deployment这个Pod镜像nginx=nginx:1.16.1:修改Pod中哪个容器的镜像,改成什么镜像?修改名为nginx的容器的镜像,镜像名改成nginx:1.16.1
如果想查看 Deployment 的上线状态,则:
kubectl rollout status deployment/nginx-deployment
得到的结果类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out
这两行日志的意思也很简单:3 个副本中的 2 个已经被更新了、deployment 上线成功

官方文档中也有介绍怎么查看上线状态

再去查看 rs ,可以看到,rs 的名称也变了,后面的哈希字符串明显不一样了

官方文档中也有介绍,重新查看 Pod,名称也变了

官方文档中上面这段话特别重要,Deploymeny 在更新时,不是先把旧的 Pod 全部删掉,重新创建。而是先创建一部分,然后删除一部分,确保一直都有 Pod 可以用,这不就是典型的 灰度发布 吗
有个细节很重点:Deployment 所创建的 Pod 数可能比期望的 Pod 数高一点点

官方文档中,这两段话也很重要。
4.2.4 查看 deployment 的描述/细节
Pod 的标签中有 pod-template-hash=6f4f7cc4d8 ,这是控制器创建 Pod 时自动给 Pod 加的,不建议删除和修改
# 查看deployment的详细信息
kubectl describe deployment nginx-deployment(deployment的名称)
# 或者
kubectl describe deployment/nginx-deployment(deployment的名称)
# 查看某一控制器的详细信息,并以yaml的格式在屏幕上展示出来
kubectl get deployment nginx-deployment(deployment的名称) -o yaml
# 查看某一控制器的详细信息,并以yaml的格式,保存到test.yaml这个文件中
kubectl get deployment nginx-deployment(deployment的名称) -o yaml >> test.yaml
# 查看某一控制器的详细信息,并以json的格式展示出来
kubectl get deployment nginx-deployment(deployment的名称) -o json

4.2.5 删除 deployment
# 删除deployment
kubectl delete -f nginx-deployment.yml (推荐,删除的更干净)
或者
kubectl delete deployment nginx-deployment
# 删除默认命名空间下的全部资源
kubectl delete all --all
# 删除指定命名空间下的全部资源
kubectl delete all --all -n 命名空间名称
建议上述命令删除 deployment ,因为这样可以把 deployment 和 Pod 一起删除干净
4.2.6 扩缩 deployment
官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/#scaling-a-deployment
deployment 和 replicaset 合二为一了,deployment 具备 replicaset 的所有功能
查询副本
kubectl get rs
或者
kubectl get replicaset

Name:Pod 名称
Desired:期望有几个副本
Current:当前有几个副本
Ready:已经准备好了几个副本
# 伸缩扩展副本
kubectl scale deployment nginx-deployment -replicas=3
或者
kubectl scale deployment/nginx-deployment -replicas=3
scale:扩展、调节的意思
deployment:扩展什么呢?扩展 deployment
nginx-deployment:扩展哪个 deployment 呢?扩展名字叫 nginx-deployment 的 deployment
replicas:副本数

上图中可以看到,扩展副本数为 3 个,查看 Pod,确实有 3 个 Pod
3 个一模一样的 Pod,不会端口冲突吗?不会。3 个 Pod 都是管理独立的容器,端口又没对外暴露,怎么会端口冲突呢
所以,现在通过浏览器是访问不了容器的,因为端口都在容器内部,都没映射出来,肯定访问不了
注意:3 个副本,不会全都分配在一台服务器上,而是随机分配到多台服务器上的

上图中设置 10 个副本,但是 Pod 有些是分配在 n2 节点上,有些是分配在 n3 节点上
注意:副本数可扩展、也可以伸缩
4.2.7 回滚 deployment
为什么要有回滚呢?比如我的项目升级到了 1.2 版本,但是上线后有 bug ,我想到原来的 1.1 版本是个稳定版本,我想回退到 1.1 版本
k8s 以什么结点认为版本该记录一下呢?我的项目从 1.1 升级到 1.2 ,K8s 怎么知道我更新了版本呢?仅当 Deployment Pod 模板(即 .sepc.template)发生改变时,例如模板的标签或容器镜像被更新,才会触发 Deployment 上线,其他更新(例如对 Deployment 执行扩缩的操作)不会触发上线动作
# 查看上线状态
kubectl rollout status deploymnet nginx-deployment(deployment的名称)
#或者
kubectl rollout status deploymnet/nginx-deployment(deployment的名称)
rollout :展示
status :状态
deploymnet :固定写法,
nginx-deploymnet :deploymnet 名称。要展示哪个 deployment 的上线状态呢,展示名字叫 nginx-deploymnet 的上线状态

如上图所示:一个名字叫 nginx-deploymnet 的 deploymnet ,成功上线
# 查看历史版本, deployment 的名称是 nginx-deploymnet
kubectl rollout history deploymnet nginx-deploymnet
# 或者
kubectl rollout history deploymnet/nginx-deploymnet

可以看到只有一个版本。现在我修改下镜像版本,把原来的 1.19 版本改成 1.21 版本

重新创建 deployment ,这里不需要删除再重新创建,直接创建就行

再查看 deployment 的上线状态

上图可以看到,deployment 已经上线完成,一个老的副本正在终止。termination 是终止的意思。稍等几秒再执行一下

上图可以看到,deployment 已经上线成功了。此时查看 Pod ,发现 Pod 的名称中的尾号已经变了,说明已经是个新的 Pod 的

再查看 Deployment 的详细信息
kubectl get deployment nginx-deployment -o yaml
可以看到,nginx 的镜像版本确实变成了 1.21

现在看下这个 deployment 有几个版本呢?
# 查看历史版本
kubectl rollout history deploymnet nginx-deploymnet(deploymnet的名称)
可以看到,有 2 个版本了,一个是 nginx:1.19 ,一个是 nginx:1.21

现在想查看某个历史版本的详细信息
# 查看某个历史版本的详细信息
kubectl rollout history deployment nginx-deployment --revision=1
1 :表示版本号
可以看到 1 版本的 nginx 镜像的版本号是 1.19

如果想看 2 版本,可以看到 2 版本的 nginx 镜像是 1.21

那现在我想回退到 1 版本怎么办?
# 回退到指定版本
kubectl rollout undo deployment nginx-deployment --to-revision=1
# 回退到上个版本
kubectl rollout undo deployment nginx-deployment
1 :表示想回退到 1 版本

上图可以看到,deployment 回退成功。但是再次查看历史版本,发现只有 2 和 3 ,没有 1 了。这是因为 1 和 3 是一样的,所以 1 就不保留了
查看 3 版本的详细信息,可以看到 3 版本的 nginx 镜像版本是 1.19 ,说明回退真的成功了

# 重新部署
kubectl rollout restart deployment nginx-deployment
尽管我的文件中写的是 1.21 版本,但是前面我已经回退到了 1.19 版本,但是文件中还是 1.21,此时我重新部署,镜像会是哪个版本呢?肯定还是 1.19 版本的
4.3 StatefulSet
4.3.1 什么是 StatefulSet
官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/statefulset
StatefulSet 是用来管理有状态应用的工作负载 API 对象
什么是无状态应用?什么是有状态应用呢?
无状态应用:应用本身不存储任何数据的应用
有状态应用:应用本身需要存储相关数据的应用
StatefulSet 用来管理某 Pod 集合的部署和扩缩(所以 StatefulSet 具备 Deployment 的一些特性),并为这些 Pod 提供持久存储和持久标识符号。
和 Deployment 类似,StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的,但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID
4.4 DaemonSet
官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/daemonset
daemonSet 是守护的意思。比如守护进程表示当前机器上必须运行的一个进程,系统开机就会运行
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为它们新增一个 Pod。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod
DaemonSet 的一些典型用法:
1、在每个节点上运行集群守护进程
2、为每个节点上运行日志收集守护进程
3、为每个节点上运行监控守护进程
5.5 Job
官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/job
Job 是任务,类似定时任务,执行完了就结束了
Job 会创建一个或多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。随着 Pod 成功结束,Job 的跟踪记录成功完成的 Pod 个数。当数量达到指定的成功个数阈值时,任务(即 Job)结束。删除 Job 的操作会清除所创建的全部 Pod。挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行
比如:运行一个 π 镜像,让它打印小数点后 2 千位,那打印完,Pod 就会成功终止
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
# ttl机制,完成任务之后多少秒之后,删除Pod
ttlSecondsAfterFinished: 100
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: [ "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)" ]
imagePullPolicy: IfNotPresent
# 当前任务出现失败,最大的重试次数
backoffLimit: 4
没有 ttlSecondsAfterFinished,则 Pod 还在,只是是终止状态

有了 ttlSecondsAfterFinished,则 Pod 会自动删除
5 Service
控制器无法为 Pod 提供网络服务,service 的作用就是为 Pod 提供网络服务
5.1 什么是 Service
官网地址:https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/
定义:将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法。说人话:就是为 Pod 提供网络服务,让我们在浏览器上可以访问
问题:如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?

如果这是一个图片处理后端,它运行了 3 个副本。这些副本是可互换的,前端不需要关心它调用了哪个后端副本。然而组成这一组后端程序的 Pod 实际上可能会发生变化,前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。Service 定义的抽象能够解耦这种关联

5.2 特性
1、Service 通过 label 关联对应的 Pod ,这点跟控制器一样
2、Service 生命周期不跟 Pod 绑定,不会因为 Pod 重新创建而改变 IP
3、提供了负载均衡功能,自动转发流量到不同 Pod
4、可对集群外部提供访问端口
5、集群内部可通过服务名字访问
5.3 Service 和 Pod 的关系
Service 能够将一个接收 port 映射到任意的 targetPort。也就是说:port 是对外开放的端口,targetPort 是 Pod 的端口
一般 Service 使用会和 Deployment 放在一个 yml 文件里,使用 3 个横杠分开,这样 K8s 集群就知道我们要创建另外一个对象
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template: # pod 的模板
metadata:
name: nginx # pod 的名称
labels:
app: nginx
spec: # pod 的规约
containers:
- name: nginx
image: nginx:1.19
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
restartPolicy: Always
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
# Service服务的名字
name: nginx
spec:
# 选择哪些Pod呢?选择标签为app=nginx的Pod
selector:
app: nginx
ports:
- protocol: TCP
# Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
port: 80
# 容器的端口
targetPort: 80
#nodePort 30124 # 节点端口,范围 30000-32767,注意此时没有指定节点端口,k8s会自动生成一个节点端口
type: NodePort
下图可以看到,创建时,不仅创建了 Deployment ,还创建了 Service

查看 Pod ,发现 Pod 也创建成功了,如下图所示:

但是查看 Service ,发现有 2 个,第一个是集群默认的,第二个才是我们新创建的。
NodePort :既可以集群内访问,也可以集群外访问。集群内访问:进入一个 Pod 中,访问另一个 Pod 的端口。上图中,集群内端口是 80 ,10.107.63.196 是集群内 IP ,只能集群内部访问,外网是访问不了 10.107.63.196 这个 IP 的
10.107.63.196 是集群内的 IP ,只能集群内访问,集群外无法访问
下图是进入一个 Pod 中

在 Pod 中访问 nginx 所在的 Pod 的 IP 和端口,可以看到,在一个 Pod 内访问另外一个 Pod ,是可以访问的

外部(比如浏览器)怎么访问 nginx 呢?下图中的 80 是 K8s 集群内部的端口,30124 才是虚拟机(或服务器)的端口,那浏览器想要访问 nginx ,就得通过服务器 IP + 30124 端口才能访问。可以看到 nginx 是运行在 k8s-n2 节点上,所以通过 k8s-n2 节点的 IP (也就是服务器 IP ,注意不是 10.244.1.42 )+30124 端口才能访问 nginx
10.244.1.41 也不是服务器的 IP ,外网也无法访问

30124 是自动生成的端口,如果想自定义节点端口(虚拟机或服务器的端口),可以在 yml 文件中这样写
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
restartPolicy: Always
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
# Service服务的名字
name: nginx
spec:
# 选择哪些Pod呢?选择标签为app=nginx的Pod
selector:
app: nginx
ports:
- protocol: TCP
# Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
port: 80
# 容器的端口
targetPort: 80
# 节点端口,供外部(比如浏览器)访问的。节点端口固定在30000 - 32767之间
nodePort: 31001
type: NodePort
重新创建 Service 之后,发现节点端口已经变了
注意:10.244.1.41 和 10.244.1.42,都不是节点 IP(虚拟机或服务器的 IP)
5.4 查看 Service
查看 service 的命令如下:
kubectl get service
# 或者简写
kubectl get svc
5.4 负载均衡
先修改 yml 文件,设置副本为 3
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
# Pod的副本数
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
restartPolicy: Always
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
# Service服务的名字
name: nginx
spec:
# 选择哪些Pod呢?选择标签为app=nginx的Pod
selector:
app: nginx
ports:
- protocol: TCP
# Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
port: 80
# 容器的端口
targetPort: 80
# 节点端口,供外部(比如浏览器)访问的。节点端口固定在30000 - 32767之间
nodePort: 30124
type: NodePort
创建后发现,有 3 个 nginx 的 Pod

查看 Service ,集群内部端口是 80 ,集群外部端口是 30124 。并且 3 个 nginx ,1 个运行在 k8s-n2 节点上,2 个运行在 k8s-n3 节点上

现在先后进入 3 个 nginx 容器,修改 index.html 文件,第 1 个改成 “nginx-1”,第 2 个改成 “nginx-2” ,第 3 个改成 “nginx-3”

现在一直访问 k8s-n3 节点的服务器 + 30124 端口,就会出现负载均衡的效果
10.15.0.23 是服务器的 ip
下图中可以看到,我访问的是同一机器的 ip ,但是出现了不同的效果,说明负载均衡生效了。但是生效时间有点长,需要多等待一下,再去刷新浏览器

用服务器访问效果可能更明显,我在服务器上一直使用 curl 命令,也能出现不同效果,说明负载均衡确实生效了

5.5 多端口
Service 可以暴露(或监听)多个端口
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
restartPolicy: Always
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
# Service服务的名字
name: nginx
spec:
# 选择哪些Pod呢?选择标签为app=nginx的Pod
selector:
app: nginx
ports:
# Service服务的端口
- protocol: TCP
port: 8080
# 给端口起个自定义的名字,表示端口的唯一标识,多端口不加name字段,创建时会报错
name: write
# 容器的端口
targetPort: 80
# 节点端口,固定在30000 - 32767 之间
nodePort: 31001
- protocol: TCP
port: 8081
name: read
targetPort: 80
nodePort: 31002
type: NodePort
Service 对集群内部暴露了 2 个端口,8080 和 8081 ,只不过都是对应 nginx 容器的容一个端口 80 。在别的 Pod中 ,访问 8080 和 8081 都一样,都能访问 nginx
Service 对节点暴露了 2 个端口,31001 和 31002 ,浏览器访问这 2 个端口都一样,都能访问 nginx
注意:Service 监听多端口的时候,必须给端口起个名字,表示端口的唯一标识,多端口不加 name 字段,创建时会报错
5.6 类型 type
前面写的 yml 文件中,最后一行 type=nodePort ,它会把当前节点的端口跟 Service 进行映射。访问流程是:访问节点端口,通过节点端口访问到对应的 Service 端口,再通过 Service 找到对应的 Pod
Kubernetes ServiceTypes 允许指定你所需要的 Service 类型
ClusterIP:在集群内部暴露Service,只能被集群内部的其他对象访问,通常用于内部服务发现,不会向集群外部暴露NodePort:将Service暴露在Node的某个端口上,从而可以通过Node的IP地址和端口来访问Service,通常用于开发和测试环境LoadBalancer:通过云服务商提供的负载均衡器来将Service暴露到公网上,使得外部用户可以访问ServiceExternalName:将Service映射到一个DNS名称上,从而可以通过DNS名称来访问Service,通常用于访问外部服务
5.6.1 ClusterIP 类型
- 这是最常见的
Service类型之一。在集群内部创建一个虚拟IP地址,它可以被其它在同一集群内的Pod访问,但不同被集群外部的请求所访问。这种类型的服务通常用于内部服务的暴露,例如数据库或者缓存服务。比如在一个 Web 应用中,你可能需要连接到一个数据库,但是这个数据库并不需要在应用之外暴露。这时候,你可以使用ClusterIP类型的Service。让应用可以访问到数据库

如上图所示,type 已经改成了 ClusterIP

创建 service 之后查看 service ,出现了 CLUSTER-IP ,也就是 10.111.47.177 ,这就是集群内 IP ,只能集群内访问,外网是访问不了的
5.6.2 NodePort 类型
- 这种类型的
Service会创建一个端口,并绑定到每个集群节点上,从而允许外部流量访问Service。这个类型通常用于公共服务的暴露,例如Web应用或者API。比如你需要在集群外部访问到一个运行在集群中的Web应用,你就可以创建一个NodePort类型的Service,通过指定Service的NodePort字段,来将Service暴露给集群外部 - 如果你将
type字段设置为NodePort,则Kubernetes控制平面将在--service-node-port-range标志指定的范围内分配端口(默认值:30000 - 32767)
5.6.3 LoadBalancer
- 这种类型的
Service类似于NodePort,但是会在云厂商中创建一个负载均衡器。这个类型通常用于在云平台上部署应用。云平台的负载均衡器将流量分发到集群中的节点。这个类型的Service只能在云平台上使用,并且需要云厂商提供支持
5.6.4 ExternalName 类型
- 这种类型的
Service允许Service到任何需要访问的ANAME DNS条目的转发。与其它类型的Service不同,它并不会代理请求到任何Pod。相反,它将请求转发到配置的外部地址。这种类型的Service通常用于将服务代理到集群外部的其他服务。比如你有一个运行在外部网络上的服务,你希望在Kubernets集群中使用该服务,这时候你可以创建一个ExternalName类型的Service,将服务的DNS解析到Kubernets集群中
5.7 Pod 之间通过 Service 通信
创建 Pod,Pod 内是 MySQL ,文件名为 mysql-1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
labels:
app: mysql
spec:
selector:
matchLabels:
app: mysql
replicas: 1
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql/mysql-server:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: root
ports:
- name: mysql
containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
selector:
app: mysql
ports:
- name: mysql
port: 3306
targetPort: 3306
type: ClusterIP
创建 Pod ,Pod 内是 nginx ,文件名为 nginx-1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
labels:
app: nginx-test
spec:
selector:
matchLabels:
app: nginx-test
replicas: 1
template:
metadata:
labels:
app: nginx-test
spec:
hostNetwork: true
containers:
- name: nginx-test
image: nginx:latest
#command: ["/bin/sh", "-c"]
#args:
#- apt-get update && apt-get install -y mysql-client && nginx -g 'daemon off;'
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-test
spec:
selector:
app: nginx-test
ports:
- name: http
port: 8081
targetPort: 80
type: ClusterIP
同时创建 2 个 Deployment ,2 个 Service ,2 个 Pod
kubectl apply -f mysql-1.yaml nginx-1.yaml

上图可以看到,mysql 和 nginx 的 Pod 已经创建成功,中间的那个 nginx 是别的脚本创建的,不用管它

进入 mysql 这个 Pod ,然后访问 nginx ,是可以访问的
这就实现了在 mysql 的 Pod 中访问 nginx