1. 基础
本文实操基于k8s 1.22.1
# 可以查看资源分配情况
kubectl describe node
# 全局资源情况查看
kubectl api-resources
1.1 apply
apply 命令可以使用配置文件创建资源
- -f 使用yaml或json创建资源, 也是比较常用的方式
kubectl apply -f ./my1.yaml # 创建资源
kubectl apply -f ./my1.yaml -f ./my2.yaml # 使用多个文件创建
kubectl apply -f ./dir # 基于目录下的所有清单文件创建资源
kubectl apply -f https://git.io/vPieo # 从 URL 中创建资源
从标准输出流创建
# 从标准输入创建多个Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: busybox-sleep
spec:
containers:
- name: busybox
image: busybox
args:
- sleep
- "1000000"
---
apiVersion: v1
kind: Pod
metadata:
name: busybox-sleep-less
spec:
containers:
- name: busybox
image: busybox
args:
- sleep
- "1000"
EOF
# 创建有多个 key 的 Secret
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: $(echo -n "s33msi4" | base64 -w0)
username: $(echo -n "jane" | base64 -w0)
EOF
1.2 get
列出一个或多个资源
类型 | 说明 |
---|---|
pods, pod, po | 列出pod信息 |
replicationcontroller, rc | 列出副本控制器 |
services, svc | 列出服务 |
ds | 列出守护程序集 |
参数 | 默认值 | 说明 |
---|---|---|
-A, --all-namespace | false | 返回所有命名空间的资源, 即使指定-n也无效 |
--chunk-size | 500 | 以块的形式返回大列表,而不是一次返回所有列表。传递0以禁用。 |
--ignore-not-found | false | true: 查找不到资源时不报错 |
-o, --output | '' | json,yaml,name,go-template,go-template-file,template,templatefile,jsonpath,jsonpath-as-json,jsonpath-file,custom-columns-file,custom-columns,wide yaml: 以yaml格式输出详细配置 wide: 以列表输出较详细的信息 name: 仅打印资源名称 json: 以json格式输出详细信息 |
--server-print | true | 是否打印从服务器接收的特定列信息, 比如: STATUS(运行状态), RESTARTS(重启次数) |
--show-labels | false | 查看对应资源设置的标签 |
--sort-by | '' | 接受 jsonpath 表达式, 比如按名称排序的pod列表 kubectl get pods --sort-by=.metadata.name |
--template | '' | |
-w, --watch | false | 监控资源变化, 开启一个常驻任务, 可使用pod name监控单个pod |
-n, --namespace | 命名空间 | |
1.3 describe
显示一个或多个资源的详细状态,默认情况下包括未初始化的资源。
2. 依赖
2.1 命名空间 namespace
- namespace的写法: namespaces, namespace, ns
将资源归类, 相关的服务放在同一个命名空间下方便管理
# 创建命名空间
kubectl create namespace
# 查询命名空间
kubectl get ns
# 删除命名空间
kubectl delete namespace NAME
# yaml 配置
apiVersion: v1
kind: Namespace
metadata:
name: repo-nexus
labels:
name: repo-nexus
k8s集群默认拥有如下几个namespace
NAME STATUS AGE
default Active 2d3h
kube-node-lease Active 2d3h
kube-public Active 2d3h
kube-system Active 2d3h
-
所有NAMESPACED的资源,在创建的时候都需要指定namespace,若不指定,默认会在default命名空间下
-
相同namespace下的同类资源不可以重名,不同类型的资源可以重名
-
不同namespace下的同类资源可以重名
-
通常在项目使用的时候,我们会创建带有业务含义的namespace来做逻辑上的整合
2.2 apiVersion
apiVersion | 含义 |
---|---|
alpha | 进入K8s功能的早期候选版本,可能包含Bug,最终不一定进入K8s |
beta | 已经过测试的版本,最终会进入K8s,但功能、对象定义可能会发生变更。 |
stable | 可安全使用的稳定版本 |
v1 | stable 版本之后的首个版本,包含了更多的核心对象 |
apps/v1 | 使用最广泛的版本,像Deployment、ReplicaSets都已进入该版本 |
快速获得资源和版本
kubectl explain pod
kubectl explain Pod.apiVersion
2.3 集群node label
# 查询node上已有的标签信息
kubectl get nodes --show-labels
# 为节点打标签
kubectl label node k8s-n-1 component=mysql
2.4 configmap
通常用来管理应用的配置文件或者环境变量, 放一些不是特别敏感的信息
apiVersion: v1
kind: ConfigMap
metadata:
name: xxxx
namespace: default
data:
# 配置 key: value
MYSQL_HOST: "172.168.23.11"
MYSQL_PORT: "3306"
kubectl create -f configmap.yaml
使用文本文件创建
MYSQL_HOST=172.168.23.11
MYSQL_PORT=3306
kubectl create configmap xxx --form-env-file=xxx.txt -n default
2.5 secret
管理敏感类信息, 默认会base64编码存储, 有三种类型
- Service Account: 用来访问Kubernetes API, 有Kubernetes自动创建, 并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中; 创建ServiceAccount后, pod中指定serviceAccount后, 自动创建该ServiceAccount对应的secret
- Opaque: base64编码格式的Secret, 用来存储密码, 密钥等;
- kubernetes.io/dockerconfigjson: 用来存储私有docker registry的认证信息
apiVersion: v1
kind: Secret
metadata:
name: xxxx
namespace: default
type: Opaque
data:
# 配置 key: base64(value)
MYSQL_USER: cm9vdA==
MYSQL_PASSWD: MTIzNDU2
kubectl create -f secret.yaml
使用文本文件创建, 不需要进行base64编码
MYSQL_USER=root
MYSQL_PASSWD=123456
kubectl create secret generic xxx --form-env-file=xxx.txt -n default
3. Pod
- pod支持的写法: pods, pod, po
docker调度的是容器,在k8s集群中,最小的调度单元是Pod
- 与容器引擎解耦 Docker、Rkt。平台设计与引擎的具体的实现解耦
- 多容器共享网络|存储|进程 空间, 支持的业务场景更加灵活
# yaml
apiVersion: v1
kind: Pod # 资源类型
metadata: # 基本配置
name: POD_NAME
namespace: MY_NS
labels: # 给pod打标签, 方便管理, 后面用到会有介绍
component: POD_NAME
spec:
containers: # 容器配置
- name: NAME1 # 容器名
image: IMAGE # 镜像仓库地址
env: # 环境变量
- name: MYSQL_HOST # 指定root用户的用户名
value: "127.0.0.1"
- name: MYSQL_PASSWD
value: "123456"
ports: # 容器暴漏的端口
- containerPort: 8002
- name: mysql # 一个pod可以拥有多个容器
image: mysql
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
- name: MYSQL_DATABASE
value: "myblog"
# 创建pod
kubectl create -f pod.yaml
kubectl apply -f pod.yaml
kubectl run --image=IMAGE POD_NAME # 更多kubectl run 用法参考kubeclt run -h
# 可以通过试运行获取基础yaml文件
kubectl run --image=nginx --dry-run -o yaml nginx
# 更新服务版本
kubectl apply -f pod.yaml
# 查看pod, -n 指定命名空间, -o wide 显示更多信息(可以看到调度节点)
kubectl get pods -o wide -n xxx
# 查看pod的明细信息及事件
kubectl describe pods -n xxx POD_NAME
# 查看pod完整的yaml
kubectl get pods -o yaml -n xxx POD_NAME
# 查看Pod内容器日志,显示标准或者错误输出日志
kubectl logs -n xxx -f POD_NAME -c CONTAINER_NAME
# --tail限定行数, 类似于docker命令 docker logs --tail=10 -f xxxxx
kubectl -n default logs -f --tail=10 nginx -c nginx
# 删除容器
kubectl delete -f pod.yaml
kubectl delete pod -n xxx POD_NAME
# 进入容器, -c 指定进入哪个容器
kubectl -n xxx exec -it POD_NAME -c NAME1 bash
# 使用 -o wide 我们可以看到pod在哪台机器, 那么对应的容器也就是在那台机器
# 我们可以在相应机器上执行 docker ps 查看容器信息
docker ps -a | grep NAME1
# pod 中的容器命名格式为: k8s_<container_name>_<pod_name>_<pod_uuid>
3.1 节点选择器
- nodeSelector 节点选择器, 可将pod调度到有某个label的node上, 如果node不存在, pod将不会正常创建, 直到node被打上相应label将会自动部署, 而不用重新创建
spec:
nodeSelector: # 使用节点选择器将Pod调度到指定label的节点
component: mysql # 指定选择有mysql标签的node
3.2 数据持久化
类似于docker的数据挂载, 但我们之前挂载都是在单机上进行, 现在我们在集群中使用volumes挂载其也仅会挂载在pod所在的机器上, 当我们重新上线时pod被调度到其它机器就依然会存在找不到数据的问题, 在这里我们使用nodeSelector解决, 当然生产上一般不会用这种方法
...
spec:
volumes:
- name: mysql-data
hostPath:
path: /opt/mysql/data
nodeSelector:
component: mysql
containers:
- name: mysql
image: mysql
...
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
使用PV+PVC连接分布式存储解决方案
- ceph
- glusterfs
- nfs
3.3 服务健康检查
两种探针
- LivenessProbe探针: 存活性探测, 用于判断容器是否存活, 即Pod是否为running状态, 如果LivenessProbe探针探测到容器不健康, 则kubelet将kill掉容器, 并根据容器的重启策略是否重启, 如果一个不包含LivenessProbe探针, 则Kubelet认为容器的LivenessProbe探针返回值永远成功
- ReadinessProbe探针: 可用性探测, 用于探测容器是否正常提供服务, 即容器的Ready是否为True, 是否可以接收请求, 如果ReadinessProbe探测失败, 则容器的Ready将为False, Endpoint Controller控制器将此Pod的Endpoint从对应的serviceEndpoint列表中移除, 不再将任何请求调度此Pod上, 直到下次探测成功. (剔除此Pod不参与接收请求不会将流量转发给此Pod)
三种类型
-
exec: 执行一个命令, 返回状态值为0则表示容器健康
-
httpGet: 发送一个http请求, 返回200-399表示容器健康
-
tcpSocket: 通过容器的IP和Port执行TCP检查, 如果能建立连接, 则表示容器健康
参数及含义
- initialDelaySeconds: 启动后第一次执行探测需要等待多少秒
- perioSeconds: 执行探测的频率. 默认10秒, 最小1秒
- timeoutSeconds: 探测超时时间. 默认1秒, 最小1秒
- successThreshold: 探测失败后, 最少连续探测成功多少次才被认定为成功. 默认1
- failureThreshold: 探测成功后, 最少连续探测失败多少次才被认定为失败. 默认3, 最小1
spec:
containers:
- image: xxx
name: xxx
livenessProbe: # 指定探针, 此处也可指定 readinessProbe
# 使用http请求, 请求容器80端口来判断容器是否存活
httpGet:
path: /blog/index
port: 80
scheme: HTTP
initialDelaySeconds: 10 # 容器启动后第一次执行探测需要等待多少秒
perioSeconds: 10 # 执行探测的频率
timeoutSeconds: 2 # 探测超时时间
3.4 重启策略
Pod的重启策略(RestartPolicy)应用于Pod内所有容器, 并仅在Pod所处的Node上由kubelet进行判断和重启操作. 当某个容器异常退出或健康检查失败时, kubelet将根据RestartPolicy的设置来进行相应的操作. Pod的重启策略包括:Always、OnFailure和Never,默认值为Always
- Always:当容器失效时(无论是否正常退出),由kubelet自动重启该容器。
- OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
- Never:不论容器运行状态如何,kubelet都不会重启该容器。
spec:
restartPolicy: Always
containers:
- name: xxx
image: xxx
args: # 模拟异常退出, 容器参数
- /bin/sh
- -c
- sleep 10 && exit 1
3.5 镜像拉取策略
spec:
containers:
- name: xxx
image: xxx
imagePullPolicy: IfNotPresent
镜像拉取策略: 默认IfNotPresent
- Always: 总是拉取镜像, 即使本地有也从仓库拉取
- IfNotPresent: 优先使用本地, 本地没有则去仓库拉取
- Never: 仅使用本地镜像, 本地没有则报错
3.6 Pod资源限制
注意:若内存使用超出限制,会引发系统的OOM机制,因CPU是可压缩资源,不会引发Pod退出或重建
spec:
containers:
- name: xxx
image: xxx
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 100Mi
cpu: 50m
requests:
- 容器使用的最小资源需求, 作用与schedule阶段, 作为容器调度时资源分配的判断依赖
- 只有当节点上可分配的资源量>=request时才允许将容器调度到该节点
- request参数不限制容器的最大可使用资源
- requests.cpu被转成docker的--cpu-shares参数, 与cgroup cpu.shares功能相同(无论宿主机有多少个cpu或内核, --cpu-shares选项都会按照比例分配cpu资源)
- requests.memory没有对应的docker参数, 仅作为k8s调度依据
limits:
- 容器能使用的资源最大量
- 设置为0表示对使用的资源不限制, 可无限使用
- 当pod内存超过limit时, 会被oom
- 当cpu超过limit时, 不会被kill, 但是会限制不超过limit值
- limits.cpu会被转为docker的-cpu-qiota参数. 与cgroup cpu.cfs_quota_us功能相同
- limits.memory会被转成docker的-memory参数. 用来限制容器最大内存
3.7 网络
如果在POD中使用"hostNetwork: true"配置网络,pod中运行的应用程序可以直接看到宿主主机的网络接口,宿主机所在的局域网上所有网络接口都可以访问到该应用程序及端口。声明pod的网络模式为host模式,效果同docker run --net=host
spec:
hostNetwork: true
3.8 引用配置
3.8.1 引用configmap
spec:
containers:
- name: xxx
image: xxx
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
name: xxx
key: MYSQL_HOST
- name: MYSQL_PORT
valueFrom:
configMapKeyRef:
name: xxx
key: MYSQL_PORT
- name: MYSQL_DATABASE
value: "xxx"
3.8.2 引用secret
spec:
containers:
- name: xxx
image: xxx
env:
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: xxx
key: MYSQL_USER
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: xxx
key: MYSQL_PASSWD
- name: MYSQL_DATABASE
value: "xxx"
3.9 pod 生命周期
Pod状态值描述
- Pending: API Server已经创建该Pod,等待调度器调度。
- ContainerCreating: 拉取镜像启动容器中
- Runnung: Pod内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态。
- Succeeded| Completed: Pod内所有容器均成功执行后退出,且不会再重启。
- Failed | Error: Pod内所有容器均已退出,但至少有一个容器退出为失败状态。
- CrashLoopBackOff: Pod内有容器启动失败,比如配置文件丢失导致主进程启动失败
- Unknown: 由于某种原因无法获取该Pod的状态,可能由于网络通信不畅导致。
初始化容器(init)
- 验证业务应用依赖的组件是否均已启动
- 修改目录的权限
- 调整系统参数
验证Pod生命周期
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
component: nginx
spec:
# 在业务pod启动前启动, 可用于处理一些业务pod启动前的一些工作
initContainers:
- name: nginx1
image: nginx
# 容器启动后写入文件时间和标记
command: ['sh', '-c', 'echo $(date +%s): INIT >> /loap/timing']
volumeMounts:
- mountPath: /loap
name: timing
containers:
- name: nginx2
image: nginx
command: ['sh', '-c', 'echo $(date +%s): START >> /loap/timing; sleep 10; echo $(date +%s): END >> /loap/timing;']
volumeMounts:
- mountPath: /loap
name: timing
livenessProbe: # 存活性探测探针
exec:
command: ['sh', '-c', 'echo $(date +%s): LIVENESS >> /loap/timing']
readinessProbe: # 可用性探测探针
exec:
command: ['sh', '-c', 'echo $(date +%s): READINESS >> /loap/timing']
lifecycle: # 生命周期
postStart: # 容器启动的时候触发
exec:
command: ['sh', '-c', 'echo $(date +%s): POST-START >> /loap/timing']
# 须主动杀掉 Pod 才会触发 pre-stop hook,如果是 Pod 自己 Down 掉,则不会执行 pre-stop hook
preStop:
exec:
command: ['sh', '-c', 'echo $(date +%s): PRE-STOP >> /loap/timing']
volumes:
- name: timing
hostPath:
path: /tmp/loap
kubectl apply -f nginx.yaml
# 查看pod所在机器
kubectl get pod -o wide
# 在pod所在机器查看文件
cat /tmp/loap/timing
1630456396: INIT
1630456398: START
1630456398: POST-START
1630456398: READINESS
1630456406: LIVENESS
1630456406: READINESS
1630456408: END
# 删除测试pod, 否则由于没有常驻进程, pod会一直重启
kubectl delete -f nginx.yaml
4. 控制器
控制器又称工作负载是用于实现管理pod的中间层,确保pod资源符合预期的状态,pod的资源出现故障时,会尝试 进行重启,当根据重启策略无效,则会重新新建pod的资源。
- ReplicaSet: 代用户创建指定数量的pod副本数量,确保pod副本数量符合预期状态,并且支持滚动式自动扩容和缩容功能
- Deployment:工作在ReplicaSet之上,用于管理无状态应用,目前来说最好的控制器。支持滚动更新和回滚功能,提供声明式配置
- DaemonSet:用于确保集群中的每一个节点只运行特定的pod副本,通常用于实现系统级后台任务。比如EFK服务
- Job:只要完成就立即退出,不需要重启或重建
- Cronjob:周期性任务控制,不需要持续后台运行
- StatefulSet:管理有状态应用
4.1 Deployment
Deployment支持的写法: deployment, deploy
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1 # 指定Pod副本数
selector: # 指定Pod的选择器
matchLabels:
app: nginx
template: # temlate中内容与创建pod基本一致
metadata:
labels: # 给Pod打label
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
# 创建Deployment
kubectl create -f nginx.yaml
kubectl create deploy --image=nginx nginx
# 通过试运行获取基础yaml
kubectl create deploy --image=nginx nginx --dry-run -o yaml
# 查看Deployment, -n 可加命名空间
kubectl get deploy
# 会自动创建replicaSet, 可以查看
kubectl get replicaset(rs)
# 使用创建文件删除Deployment
kubectl delete -f nginx.yaml
# 更新--修改配置文件后执行apply
kubectl apply -f nginx.yaml
# 在线更新, 类似于vim编辑器, 保存后自动更新
kubectl edit deploy nginx
# 直接使用命令更新
kubectl set image deploy nginx nginx=nginx:1.21.1 --record
4.2 副本数
controller(控制器)实时检测pod状态,并保障副本数一直处于期望的值
spec:
replicas: 1 # 指定Pod副本数
# 修改副本数
kubectl scale deploy nginx --replicas=2
# 最好是通过修改配置文件中replicas参数重新apply来修改副本数(保持配置文件与实际运行状况一致)
# 我们删除一个pod, 控制器会重新创建一个
kubectl delete pod nginx-6799fc88d8-7rppv
[root@k8s-n-1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6799fc88d8-7rppv 1/1 Running 0 9m5s
nginx-6799fc88d8-fnwqr 1/1 Running 0 56s
[root@k8s-n-1 ~]# kubectl delete pod nginx-6799fc88d8-7rppv
pod "nginx-6799fc88d8-7rppv" deleted
[root@k8s-n-1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6799fc88d8-fnwqr 1/1 Running 0 97s
nginx-6799fc88d8-grxfs 0/1 ContainerCreating 0 2s
4.3 Pod驱逐策略
K8S 有个特色功能叫 pod eviction,它在某些场景下如节点 NotReady,或者资源不足时,把 pod 驱逐至其它节点,这也是出于业务保护的角度去考虑的
4.4 更新策略
spec:
replicas: 2 # 指定Pod副本数
selector: # 指定Pod的选择器
matchLabels:
app: myblog
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate # 指定更新方式为滚动更新,默认策略,通过kubectl get deploy -o yaml查看
策略控制:
- maxSurge:最大激增数, 指更新过程中, 最多可以比replicas预先设定值多出的pod数量, 可以为固定值或百分比,默认为desired Pods数的25%。计算时向上取整(比如3.4,取4),更新过程中最多会有replicas + maxSurge个pod
- maxUnavailable: 指更新过程中, 最多有几个pod处于无法服务状态 , 可以为固定值或百分比,默认为desired Pods数的25%。计算时向下取整(比如3.6,取3)
# 我们修改一下nginx镜像版本kubectl set image deploy nginx nginx=nginx:1.21.1 --record# 查看滚动更新事件kubectl describe deploy nginx
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 35s deployment-controller Scaled up replica set nginx-54f48578cf to 1 Normal ScalingReplicaSet 32s (x2 over 5m5s) deployment-controller Scaled down replica set nginx-6799fc88d8 to 2 Normal ScalingReplicaSet 32s deployment-controller Scaled up replica set nginx-54f48578cf to 2 Normal ScalingReplicaSet 29s deployment-controller Scaled down replica set nginx-6799fc88d8 to 1 Normal ScalingReplicaSet 29s deployment-controller Scaled up replica set nginx-54f48578cf to 3 Normal ScalingReplicaSet 27s deployment-controller Scaled down replica set nginx-6799fc88d8 to 0
- 我们有三个节点, 所以最多可以增加1个pod, 最多可以有0个pod处于无法服务状态(即至少要保持三个pod在运行中)
- 3 old pod, 增加的策略触发成功增加一个pod, 减少的策略触发失败
- 3 old pod + 1 new pod, 增加的策略触发失败, 减少的策略触发成功, 减少一个旧pod
- 2 old pod + 1 new pod, 增加1减少0
- 2 old pod + 2 new pod, 增加0减少1
- 1 old pod + 2 new pod, 增加1减少0
- 1 old pod + 3 new pod, 新的pod数满足需求, 旧的pod减少1
- 3 new pod 完成更新
4.5 服务回滚
通过滚动升级的策略可以平滑的升级Deployment,若升级出现问题,需要最快且最好的方式回退到上一次能够提供正常工作的版本。为此K8S提供了回滚机制
revision:更新应用时,K8S都会记录当前的版本号,即为revision,当升级出现问题时,可通过回滚到某个特定的revision,默认配置下,K8S只会保留最近的几个revision,可以通过Deployment配置文件中的spec.revisionHistoryLimit属性增加revision数量,默认是10。
# 查看当前版本及版本历史, 如果是<none>是因为创建和更新的时候没有加--record
kubectl rollout history deploy nginx
# 我们删除服务重建一下
kubectl delete -f nginx.yaml
kubectl apply -f nginx.yaml --record
# 然后修改一下
kubectl set image deploy nginx nginx=nginx:1.21.1 --record
# 重新查看历史版本
kubectl rollout history deploy nginx
[root@k8s-n-1 ~]# kubectl rollout history deploy nginx
deployment.apps/nginx
REVISION CHANGE-CAUSE
1 kubectl apply --filename=nginx.yaml --record=true
2 kubectl set image deploy nginx nginx=nginx:1.21.1 --record=true
# 回滚到具体的版本
kubectl rollout undo deploy nginx --to-revision=1
[root@k8s-n-1 ~]# kubectl rollout undo deploy nginx --to-revision=1
deployment.apps/nginx rolled back
[root@k8s-n-1 ~]# kubectl rollout history deploy nginx
deployment.apps/nginx
REVISION CHANGE-CAUSE
2 kubectl set image deploy nginx nginx=nginx:1.21.1 --record=true
3 kubectl apply --filename=nginx.yaml --record=true
我们看到本该再增加一条记录的历史记录却只有两条记录, 是因为1和3是同一个版本, 则只保留一条
5. Service
service别名: services, service, svc
通过上面,能够通过Deployment来创建一组Pod来提供具有高可用性的服务。虽然每个Pod都会分配一个单独的Pod IP,然而却存在如下两个问题:
- Pod IP仅仅是集群内可见的虚拟IP,外部无法访问
- Pod IP会随着Pod的销毁而消失,当ReplicaSet对Pod进行动态伸缩时,Pod IP可能随时随地都会变化,这样对于访问这个服务带来了难度
5.1 Service负载均衡之Cluster IP
service是一组pod的服务抽象,相当于一组pod的LB,负责将请求分发给对应的pod。service会为这个LB提供一个IP,一般称为cluster IP 。使用Service对象,通过selector进行标签选择,找到对应的Pod
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: ClusterIP
# 创建
kubectl apply -f svc-nginx.yaml
kubectl expose deployment nginx --port=80 --type=ClusterIP
# 查看svc
kubectl get svc
# 删除
kubectl delete svc nginx
# 查看svc详情
kubectl describe svc nginx
[root@k8s-n-1 ~]# kubectl describe svc nginx
Name: nginx
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx # 根据此label筛选其可操控的pod
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.1.156.133
IPs: 10.1.156.133
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.2.1.12:80,10.2.2.7:80
Session Affinity: None
Events: <none>
我们现在就可以在集群内部用 10.1.156.133 来访问服务了
5.1.1 Endpoint
service对象创建的同时,会创建同名的endpoints对象,若服务设置了readinessProbe, 当readinessProbe检测失败时,endpoints列表中会剔除掉对应的pod_ip,这样流量就不会分发到健康检测失败的Pod中
# 查看endpoint, 与service中的Endpoints内容一致
kubectl get endpoints nginx
5.1.2 存在的问题
目前使用hostNetwork部署, 需通过宿主机ip+port访问, 就会有以下问题
- 服务使用hostNetwork,使得宿主机的端口大量暴漏,存在安全隐患
- 容易引发端口冲突
服务均属于k8s集群,尽可能使用k8s的网络访问,因此可以对目前myblog访问mysql的方式做改造:
- 为mysql创建一个固定clusterIp的Service,把clusterIp配置在myblog的环境变量中
- 利用集群服务发现的能力,组件之间通过service name来访问
5.2 服务发现
在k8s集群中,组件之间可以通过定义的Service名称实现通信
虽然podip和clusterip都不固定,但是service name是固定的,而且具有完全的跨集群可移植性,因此组件之间调用的同时,完全可以通过service name去通信,这样避免了大量的ip维护成本,使得服务的yaml模板更加简单。
# 再起一个服务, 来验证一下
kubectl create deployment tomcat --image=tomcat
kubectl expose deployment tomcat --port=8080 --type=ClusterIP
# 再tomcat容器中请求nginx服务
kubectl exec -it tomcat-7d987c7694-qgfxv -- bash
# 可以直接使用svc name访问服务
curl nginx
# 查看pod解析配置
cat /etc/resolv.conf
root@tomcat-7d987c7694-qgfxv:~# cat /etc/resolv.conf
nameserver 10.1.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
# 上面的10.1.0.10是dns的ip
[root@k8s-n-1 ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.1.0.10 <none> 53/UDP,53/TCP,9153/TCP 10d
启动pod的时候,会把kube-dns服务的cluster-ip地址注入到pod的resolve解析配置中,同时添加对应的namespace的search域。 因此跨namespace通过service name访问的话,需要添加对应的namespace名称
# 根据resolv.conf的配置, 下述地址都可请求nginx服务
curl nginx
curl nginx.default # 可实现跨命名空间通讯
curl nginx.default.svc
curl nginx.default.svc.cluster.local
5.3 Service负载均衡之NodePort
cluster-ip为虚拟地址,只能在k8s集群内部进行访问,集群外部如果访问内部服务,实现方式之一为使用NodePort方式。NodePort会默认在 30000-32767 ,不指定的会随机使用其中一个
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: NodePort
# 创建
kubectl apply -f svc-nginx.yaml
kubectl expose deployment nginx2 --port=80 --type=NodePort
# 启动后就可用机器ip(任意node都可以)+port访问nginx服务了
curl 192.169.8.1:31429
curl 192.169.8.2:31429
curl 192.169.8.3:31429
我们也可以在同一局域网内的浏览器访问到nginx服务
5.4 kube-proxy
运行在每个节点上,监听 API Server 中服务对象的变化,再通过创建流量路由规则来实现网络的转发。官方文档
有三种模式:
- User space, 让 Kube-Proxy 在用户空间监听一个端口,所有的 Service 都转发到这个端口,然后 Kube-Proxy 在内部应用层对其进行转发 , 所有报文都走一遍用户态,性能不高,k8s v1.2版本后废弃
- Iptables, 当前默认模式,完全由 IPtables 来实现, 通过各个node节点上的iptables规则来实现service的负载均衡,但是随着service数量的增大,iptables模式由于线性查找匹配、全量更新等特点,其性能会显著下降
- IPVS, 与iptables同样基于Netfilter,但是采用的hash表,因此当service数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能。 k8s 1.8版本开始引入,1.11版本开始稳定,需要开启宿主机的ipvs模块
iptables模式示意图:
[root@k8s-n-1 ~]# iptables-save | grep "default/nginx"
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx" -m tcp --dport 31429 -j KUBE-SVC-2CMXP7HKUVJN7L6M
-A KUBE-SEP-QEWF37NALXUNKCQB -s 10.2.1.13/32 -m comment --comment "default/nginx" -j KUBE-MARK-MASQ
-A KUBE-SEP-QEWF37NALXUNKCQB -p tcp -m comment --comment "default/nginx" -m tcp -j DNAT --to-destination 10.2.1.13:80
-A KUBE-SEP-THS5ZWJX3A5H6VN4 -s 10.2.1.12/32 -m comment --comment "default/nginx" -j KUBE-MARK-MASQ
-A KUBE-SEP-THS5ZWJX3A5H6VN4 -p tcp -m comment --comment "default/nginx" -m tcp -j DNAT --to-destination 10.2.1.12:80
-A KUBE-SERVICES -d 10.1.162.20/32 -p tcp -m comment --comment "default/nginx cluster IP" -m tcp --dport 80 -j KUBE-SVC-2CMXP7HKUVJN7L6M
-A KUBE-SVC-2CMXP7HKUVJN7L6M ! -s 10.2.0.0/16 -d 10.1.162.20/32 -p tcp -m comment --comment "default/nginx cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-2CMXP7HKUVJN7L6M -p tcp -m comment --comment "default/nginx" -m tcp --dport 31429 -j KUBE-MARK-MASQ
-A KUBE-SVC-2CMXP7HKUVJN7L6M -m comment --comment "default/nginx" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-THS5ZWJX3A5H6VN4
-A KUBE-SVC-2CMXP7HKUVJN7L6M -m comment --comment "default/nginx" -j KUBE-SEP-QEWF37NALXUNKCQB
6. Kubernetes服务访问之Ingress
对于Kubernetes的Service,无论是Cluster-Ip和NodePort均是四层的负载,集群内的服务如何实现七层的负载均衡,这就需要借助于Ingress,Ingress控制器的实现方式有很多,比如nginx, Contour, Haproxy, trafik, Istio。
Ingress-nginx是7层的负载均衡器 ,负责统一管理外部对k8s cluster中Service的请求。主要包含:
- ingress-nginx-controller:根据用户编写的ingress规则(创建的ingress的yaml文件),动态的去更改nginx服务的配置文件,并且reload重载使其生效(是自动化的,通过lua脚本来实现);
- Ingress资源对象:将Nginx的配置抽象成一个Ingress对象
实现逻辑
-
ingress controller通过和kubernetes api交互,动态的去感知集群中ingress规则变化
-
然后读取ingress规则(规则就是写明了哪个域名对应哪个service),按照自定义的规则,生成一段nginx配置
-
再写到nginx-ingress-controller的pod里,这个Ingress controller的pod里运行着一个Nginx服务,控制器把生成的nginx配置写入/etc/nginx/nginx.conf文件中
-
然后reload一下使配置生效。以此达到域名分别配置和动态更新的问题
6.1 安装
# 一般多试几次就能下载下来
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.0/deploy/static/provider/baremetal/deploy.yaml
sed -i "s#k8s.gcr.io/ingress-nginx/controller:v1.0.0.*#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.0.0#" deploy.yaml
sed -i "s#k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.0.*#hzde0128/kube-webhook-certgen:v1.0#g" deploy.yaml
kubectl apply -f deploy.yaml
kubectl get pod -n ingress-nginx -owide
kubectl describe pod -n ingress-nginx
# 查看webhook
kubectl get validatingwebhookconfigurations
# 删除ingress-nginx-admission
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
6.2 使用
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
# 可通过kubectl get ingressclass 查看ingressClassName
ingressClassName: nginx
rules:
- host: nginx.openstudy.space
http:
paths:
- path: /
pathType: Prefix
backend:
service:
# 服务的service name
name: nginx
port:
# 服务开放的端口
number: 80
kubectl apply -f ingress.yaml
# 没有域名可以添加hosts测试, 换成自己的ip
vi /etc/hosts
# 192.169.8.1 nginx.openstudy.space
yum -y install
ifconfig cni0 down
ifconfig flannel.1 down
ifconfig del flannel.1
ifconfig del cni0
ip link del flannel.1
ip link del cni0
yum -y install bridge-utils
brctl delbr flannel.1
kubectl delete -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
ip link del flannel.1
ip link del cni0
rm -rf /var/lib/cni/
rm -f /etc/cni/net.d/*
systemctl restart kubelet
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml