Kubernetes
为什么要K8s?
用 Docker 进行容器化管理之后方便了很多,容器少的话,可以使用 Shell 脚本来管理。但随着容器越来越多,容器也越来越难以管理,项目架构也越来越复杂,如何管理和维护这些容器,就是 Kubernetes 要解决的问题。
其实K8s就是管理docker容器的一个组件。在容器数量少的情况下,docker足够了,可是数量大了之后,就需要专门的工具来了
为什么叫K8s?
K指首字母,s指末尾字母,8指中间有8个单词
初步理解
传统方式下,一台linux服务器一般部署一个服务。那当要部署集群就是要多态linux服务器了。
但是在有了容器化部署之后就可以不必要这样了。以前是1对1的关系,现在是n对m的关系了。
那可能有人问:n:m比1:1的模式好嘛?不会性能下降嘛?
不会!容器化部署支持一台linux服务上部署多台应用,又因为容器的隔离性和动态调整。不仅不会性能下降,还充分提高了资源利用率!
Kubernetes 架构
Kubernetes 是典型的 Master-Worker 架构, Master Node 负责管理整个集群,Worker Node 负责运行应用程序和服务。
Master-Node是单独一个linux,worker Node也是单独一个Linux。并且Master一般是用来管理worker的,容器部署worker上,master是不会部署容器的。
这里有两个基本概念
一个是node,node就是指一个单独的linux服务器
另一个是pod,pod在K8s中指的是一个或多个容器,(就是docker那种)。但是我们一般只会在pod里放一个容器,不会放多个容器。
只有一些高度耦合的容器才会放在一个pod中
minikube
这是一个用来进行学习的轻量级K8s集群环境部署
windows上记得打开docker desktop。
# macOS
brew install minikube
# Windows
choco install minikube
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
启动minikube
这会直接创建一个单节点集群环境。
单节点集群环境指的是master和worker都在一台机子上了。这是作为学习的,企业中就不会使用这个
# 启动minikube
minikube start
K3s进行集群环境搭建
准备3台Linux服务器,保证相互之间可以ping通
创建master
在其中一台上执行这个指令 curl -sfL https://get.k3s.io | sh -
curl -sfL https://get.k3s.io
:从远程服务器下载一个安装脚本(其实是一个.sh
脚本文件)。
| sh -
:把下载到的这个脚本直接传给 shell 去执行。
这个脚本的指令干什么的
✅ 1. 环境准备
检查是否是 root 用户
检查系统是否支持 systemd(用来托管服务)
关闭 swap(如果没关)
检查系统内核、iptables、cgroups 等环境
✅ 2. 下载并安装 k3s
✅ 3. 启动 k3s 作为服务
✅ 4. 生成配置文件和 token
生成集群配置文件(kubeconfig):
路径:
/etc/rancher/k3s/k3s.yaml
生成集群 token(用于其他节点加入):
路径:
/var/lib/rancher/k3s/server/node-token
创建worker
在 Master 节点查看加入命令:
sudo cat /var/lib/rancher/k3s/server/node-token
在 Worker(两外两个linux) 节点执行(替换
<MASTER_IP>
和<NODE_TOKEN>
):
curl -sfL https://get.k3s.io | K3S_URL=https://<MASTER_IP>:6443 K3S_TOKEN=<NODE_TOKEN> sh -
worker只要获得master的ip和token凭证就可以加入集群了
kubectl
你可能在想,我怎么来控制这个集群呢?
这里有一个专门控制集群的东西,叫kubectl。
在之前的minikube里已经安装了kubectl
在k3s里的master里也安装了kubectl,注意,这里从节点的linux是无法使用kubectl指令的
可是,一个可以在windows上操作k8s集群的方法嘛?有的有的
你只需要先在windows的命令行上安装kubectl
curl -LO https://dl.k8s.io/release/$(curl -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
把master上的文件内容拷贝下来
sudo cat /etc/rancher/k3s/k3s.yaml
把内容拷贝到跳板机的 ~/.kube/config
文件中。
注意:
你需要修改里面的
server: https://127.0.0.1:6443
这一行,把 IP 改成 master 的实际 IP(例如192.168.1.10
)或者你可以 scp 拷贝过去:
bash
复制编辑
scp master:/etc/rancher/k3s/k3s.yaml ~/.kube/config
并执行:
bash
复制编辑
sed -i 's/127.0.0.1/<master的真实IP>/' ~/.kube/config
这样你就可以在windows上使用kubectl来操作k8s集群了
pod
#已经不建议用了,因为就不建议直接创建pod,而是使用deployment来间接创建对象
kubectl run nginx001 --image=nginx
#查看pod
kubectl get pod
deployment
为什么要deployment?
因为如果要构建集群保证高可用的话,怎么统一管理这个集群呢?
这就是deployment了,这里面一般包含多个pod,并且这些pod都是相同业务的,只是为了保证高可用而创建多个。
deployment和pod之间还有一个replicaset。replicaset是用来管理pod的,比如说,如果有一个pod宕机了,那么replicaset就会自动创建一个新的来保证。
但是为什么要有这一层呢,直接deployment和pod不行嘛?
因为在更新的时候需要一个deployment对应多个replicaset
在正常情况下:
一个 Deployment 会创建 一个活跃的 ReplicaSet
每当你 修改 Deployment(比如更新镜像),K8s 会:
创建一个新的 ReplicaSet(新版本)
缩容旧的 ReplicaSet(旧版本)
等新 ReplicaSet 的 Pod 都 Ready 后,删除旧 ReplicaSet(取决于配置)
所以在滚动更新过程中,一个 Deployment 下会同时存在多个 ReplicaSet:新旧共存,逐渐替换。
命令行创建
#create 对应组件 组件名称 --image=镜像名称
kubectl create deployment nginx-deployment --image=nginx#这个deployment内只含有一个pod
kubectl create deployment nginx-deployment --image=nginx --replicas=3#这个deployment内含有3个pod
#查看deployment
kubectl get deployment
配置文件创建
vim nginx-deployment.yaml
apiVersion: apps/v1 # 定义使用的API版本,这里是apps/v1,表示使用应用的v1版本API。
kind: Deployment # 定义资源类型为Deployment,表示部署一个应用。
metadata: # 元数据部分,用于描述Deployment的基本信息。
name: nginx-deployment # Deployment的名称为nginx-deployment。
spec: # 规格部分,定义Deployment的规格。
selector: # 选择器部分,用于选择要管理的Pod。
matchLabels: # 匹配标签部分。
app: nginx # 匹配标签为app=nginx的Pod。
replicas: 3 # 指定副本数为3,意味着会运行3个相同的Pod实例。
template: # 模板部分,定义创建新Pod时使用的模板。
metadata: # 模板的元数据部分。
labels: # 标签部分,为新创建的Pod定义标签。
app: nginx # 新创建的Pod的标签为app=nginx。
spec: # 新创建的Pod的规格。
containers: # 容器列表,定义在Pod中运行的容器。
- name: nginx # 容器的名称为nginx。
image: nginx:1.25 # 使用的容器镜像是nginx:1.25。
ports: # 容器的端口配置。
- containerPort: 80 # 容器监听的端口号为80。
kubectl create -f nginx-deployment.yaml#创建deployment
kubectl delete -f nginx-deployment.yaml#删除deployment
kubectl apply -f nginx-deployment.yaml#更新deployment,自动修改服务
Service
你想想,里面是集群,那么我怎么连接deployment来用上服务呢?
以及这里的pod是内部ip,外网怎么访问到呢
这时候就要用到我们的svc了
#直接暴露这个服务,不过这个默认是ClusterIP,只能集群内部访问
kubectl expose deployment nginx-deployment
配置文件的方式 vim nginx-service.yaml
内部服务
apiVersion: v1 # 使用的Kubernetes API版本,这里是v1。
kind: Service # 定义资源类型为Service,表示创建一个服务。
metadata: # 元数据部分,用于描述Service的基本信息。
name: nginx-service # Service的名称为nginx-service。
spec: # 规格部分,定义Service的规格。
selector: # 选择器部分,用于指定服务应该选择哪些Pod作为后端。
app: nginx # 选择具有标签app=nginx的Pod作为后端。
ports: # 端口配置,定义Service暴露的端口。
- protocol: TCP # 使用TCP协议。
port: 80 # Service暴露的端口号为80。
targetPort: 80 # 转发到后端Pod的端口号也为80。
kubectl apply -f nginx-service.yaml
apiVersion: v1 # 使用的Kubernetes API版本,这里是v1。
kind: Service # 定义资源类型为Service,表示创建一个服务。
metadata: # 元数据部分,用于描述Service的基本信息。
name: nginx-service # Service的名称为nginx-service。
spec: # 规格部分,定义Service的规格。
type: NodePort # 指定服务类型,这里如果有的话,就是外部服务了
selector: # 选择器部分,用于指定服务应该选择哪些Pod作为后端。
app: nginx # 选择具有标签app=nginx的Pod作为后端。这里前面的deployment中也写了这个。对应起来了
ports: # 端口配置,定义Service暴露的端口。
- protocol: TCP # 使用TCP协议。
port: 80 # Service暴露的端口号为80。
targetPort: 80 # 转发到后端Pod的端口号也为80。
nodePort: 30080 #端口范围在30000-32767之间
kubectl apply -f nginx-service.yaml
这个svc既可以从内部80访问,也可以从外部30080访问
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-clusterip ClusterIP 10.43.2.15 <none> 80/TCP 10m
myapp-nodeport NodePort 10.43.2.32 <none> 80:30080/TCP 10m
CLUSTER-IP
这个只适合在内部访问,外部访问的话是用linux的ip和port
其他常见指令
#查看nodes
kubectl get nodes
#查看日志
kubectl log pod名字
#进入容器
kubectl exec -it pod名字 /bin/bash
#删除组件
kubectl delete 组件类型 组件名称
kubectl delete deployment nginx-deployment
#查看全部
kubectl get all
#看pod的ip
kubectl get nodes -o wide
portainer
可视化界面,很简单
基本概念
Node
Node:节点,一个物理机或者一台虚拟机。
Pod
Pod 是 Kubernetes 的最小调度单元,可以理解为容器的抽象。一个 Pod 就是一个或者多个应用容器的组合。它创建了一个容器的运行环境,在这个环境中容器可以共享一些资源,比如网络、存储和运行时的一些配置等等。
假设我们系统包括一个应用程序和一个数据库,就可以将应用程序和数据库分别放到两个不同的 Pod 中,一般情况下一个 Pod 中只运行一个容器,这样可以更好地实现应用程序的解耦和扩展。
一个 Pod 中也是可以运行多个容器的,一般仅限于这些容器是高度耦合的情况,它们之间为了共享一些配置或者资源,不得不将它们放到一个容器中
应用程序要访问数据库的话,只需要知道数据库的 IP 地址,这里的 IP 地址是 Pod 在创建的时候自动创建的,是一个集群内部的 IP 地址(也就是无法从集群外部访问),Pod 之间通过这些 IP 地址进行通信。
Service
为了解决这个问题,Kubernetes 提供了一个名为 Service 的资源对象,它可以将一组 Pod 封装成一个服务,这个服务通过一个统一的入口来访问。
就比如上面的场景,我们分别将应用程序和数据库两组 Pod 封装成两个 Service,这样应用程序就可以通过 Service 的 IP 地址访问数据库(有点像路由器和反向代理),即使 Pod 的 IP 地址发生了变化,Service 的 IP 地址也不会发生变化,Service 会自动将请求转发到其它健康的 Pod 上。
正向代理:代理的是 C 端(客户端),S 端不知道 C 端的 IP。C 端发送请求给代理服务器,再由代理服务器向 S 端(服务端)发送请求,S 端就会将数据响应给代理服务器,再由代理服务器将数据传输给 C 端。(科学上网使用的就是正向代理)
反向代理:代理的是 S 端,C 端不知道 S 端的 IP。S 端可能有多台服务器,C 端向代理服务器发送请求,代理服务器向 S 端的其中一台服务器发送请求,服务器将数据响应给代理服务器,再由代理服务器将数据传输给 C 端。(比如 Nginx 的负载均衡)
使用代理模式的好处:
隐藏真实 IP,隐私
在代理服务器上设置缓存可以加快访问
突破网络限制,有些网站会限制某些地区的 IP 访问,使用化茧成蝶可以突破限制。(还是科学上网)
坏处:
C 端和 S 端的 IP 对彼此隐藏了,但会将 IP 暴露给代理服务器
网络链路多了代理服务器的节点,降低访问速度
内部服务和外部服务
内部服务:不能暴露或者不需要暴露给外部的服务,比如数据库、缓存、消息队列等,这些服务只需要在集群内部访问就可以了。
外部服务:后端的 API 接口或者前端界面等等,这些就是需要暴露给用户的服务。
外部服务常见的类型有 ExternalName、LoadBalancer、NodePort、ClusterIP,其中 NodePort 是我们常用的类型,它会在节点上开放一个端口,然后将这个端口映射到 Service 的 IP 地址和端口上,这样就可以通过节点的 IP 地址和端口来访问 Service 了。是不是感觉有点熟悉?http://localhost:8080,想起来了吗?
在开发和测试阶段使用 IP 和端口号的方式是没有问题的,但在生产环境中通常是用域名来访问服务的,这时就用到了另一个资源对象 Ingress。
Ingress
Ingress 是用于管理从集群外部访问集群内部服务的入口和方式,可以通过 Ingress 配置不同的转发规则,从而根据不同的规则来访问不同的 Service 以及 Service 所对应的 Pod。还可以通过 Ingress 来配置域名,这样就可以从集群外部使用域名和访问 Service。
Ingress 也可以用来配置 SSL 证书或者负载均衡。
ConfigMap
原来我们的应用程序需要访问数据库的话,一般的做法是将数据库的地址和端口等连接信息写到配置文件或者环境变量中,然后在应用程序中读取这些配置信息,这样配置信息就和应用程序耦合在一起了,当数据库的地址或者端口发生变化,我们就得修改应用程序的配置信息然后重新编译部署,这样不仅麻烦,而且对于一些需要不间断运行的服务来说是不能接受的(比如你深夜肚子饿了想点外卖而服务器却在重新编译部署)。
为了解决这个问题,Kubernetes 提供了一个 ConfigMap 组件,它可以将配置信息封装起来,然后就可以在应用程序中读取和使用。将配置信息和应用程序的镜像内容分离开,当数据库的地址和端口发生变化的时候,只需要修改 ConfigMap对象中的配置信息,然后重新加载 Pod,不需要重新编译和部署应用程序。
可以理解为给数据库加了个反向代理
Secret
但 ConfigMap 有个问题,就是它的配置信息是明文的,如果用户名和密码存在 ConfigMap 中是有风险的,于是 Kubernetes 提供了 Secret 组件,用于封装敏感信息,会将配置信息 Base64 编码,但 Base64 编码很容易解码,浏览器随便搜个解码器就能得到原文,所以还需要配合其它手段来确保安全性。
真是一层套一层
Volume
Pod 被销毁或重启时数据也跟着消失,这对于需要持久化存储的应用程序比如数据库肯定是不行的,Kubernetes 提供了 Volume 组件,它可以将一些持久化存储的资源挂载到集群中的本地磁盘上,或者挂载到集群外部的远程存储上(比如 OSS)。
Deployment
如果服务端只有一个节点的话,这个节点发生故障就会导致服务宕机(即单点故障),无法实现高可用性。这个好解决,既然一个不够,那就多复制几个,当一个节点发生故障的时候,Service 就会自动将请求转发到另一个节点。(这里的 Service 指的是上面的 Service 组件)
Deployment 就是用来定义和管理应用程序的副本数量以及应用程序的更新策略,将一个或者多个 Pod 组合在一起,简化应用程序的部署和更新操作,还可以副本控制、滚动更新、自动扩缩容等。
副本控制:定义和管理应用程序的副本数量,比如定义一个应用程序副本数量为 3,当其中一个发生故障时,就会生成一个新的副本来替换坏副本,始终保持有 3 个副本在集群中运行。
滚动更新:定义和管理应用程序的更新策略,使用新版本替换旧版本,确保应用程序的平滑升级。
平滑升级:不对用户的使用造成中断或不便的升级
StatefulSet
除了应用程序,数据库也有故障、升级和更新维护的时候,数据库停了服务也停了,所以数据库也需要采取多副本的方式来保证高可用性,但一般不使用 Deployment 来实现数据库的多副本,因为数据库的多副本之间是有状态的,就是每个副本的数据存在差异(状态不同),需要确保数据的一致性,可以把数据写到一个共享的存储中或者同步不同副本之间的数据。
对于这一类有状态的应用程序,Kubernetes 提供了 StatefulSet 组件来管理,StatefulSet 跟 Deployment 非常类似,也提供了定义和管理应用程序副本数量和动态扩缩容等功能,此外它还保证了每个副本都有自己稳定的网络标识符和持久化存储,数据库、缓存、消息队列等这些有状态的应用以及保留了会话状态的应用一般都需要使用 StatefulSet 而不是 Deployment。
但 StatefulSet 部署的过程比较复杂和繁琐,而且并不是所有的有状态应用都适合用 StatefulSet 来部署,比如Redis,当 Redis 实例需要使用内存进行数据存储,并且数据存储在实例的内存中而不是持久性存储中时,如果这时候实例重启,Redis 数据丢失,这时候 StatefulSet 就无法保证数据的持久性(还是有点不理解)。更加通用和简单的方式是把有状态的应用程序从 Kubernetes 中剥离出来,在集群外单独部署。(ToLearn:集群外单独部署有状态应用的方法)
推荐资料
视频:https://www.bilibili.com/video/BV1Se411r7vY/
文档:https://www.yuque.com/xiaoguai-pbjfj/cxxcrs/ocefqltbmbgl5eqg?singleDoc#AtBrM