由于没有资源,也不想开虚拟机,所以只停留在理论学习。

K8s 学习

对我的理解,Docker 就是箱子,服务器里面的 Docker 就是将他们做成集装箱,K8s 可以将这些集装箱集中在自己的船上,一条船可以搭上数百个集装箱。是一个分布式的系统框架。

注意下面的 YAML 语法,不能用 Tab!

理解特性

K8s 可以实现负载均衡与服务发现,会通过轮询分担网络流量。可以互相测每个集群的健康度,有问题会自动切换。

K8s 可以管理每个集群分配存储空间;

K8s 可以实现自动部署或回滚,可以回滚容器到指定的版本;

K8s 的 Pod 里面的容器,他可以自动分配或自己指定每个容器的 CPU 和内存;

K8s 可以实现自我修复,如果某个应用部署到其中一个集群中,但这个集群突然故障了,K8s可以在另外的集群重新拉起这个应用。

架构

一般是集群模式。Master + Worker 模式,Master 是领导,Worker 是员工。n Master + n Worker Node,n 要大于或等于1

如果只有一个领导的话,领导寄了公司就会乱套;如果有多个领导,领导会成立一个董事会,如果A领导挂掉了,董事会的各位领导会选举一名新的领导,少数服从多数,大家都同意 B 领导可以当主领导才能当主领导。

Master 可以当 Worker 部署应用,但一般不用于跑业务。

组件架构

架构图,Cluster 是集群,我们当成集团吧。整个模式可以理解为中介模式,类似 Zabbix Proxy。

  • Node :代表服务器,工厂。

    • kubelet:服务器管理 Agent。监控当前节点的所有情况,控制器。
    • kube-proxy:控制网络访问的出入,相当于路由器。
  • Control Plane :控制中心,就是上面的 Master Node,集团的总部,负责安排工作给 Node 干!

    • c-m:Control manager,决策者,决定干什么。
    • etcd:数据库或资料库,用来存储核心。
    • api:接口,秘书,工人想联系 c-m 得经过我的 API。
    • sched(scheduler):调度者,负责决定这个项目由哪个工人去干,看这些应用在哪个服务器运行比较优越。
  • cloud provider manger:云决策者

sched 决定哪个节点跑,这些基础数据都会存到 etcd;Node 无法直接联系到 C-M 和 sched,数据交互都得经过 api。所有部件都得经过 api,不可以单独找 c-m。

运作架构

每台服务器都要安装 Docker 或容器运行环境。kubelet 负责每个应用的启停销毁,探测每个应用的状态。

k-proxy 控制每个应用容器之间的访问,发现其他节点的应用。

kubectl 是命令行模式,我们都是用这个跟 k8s 集群进行交互,用户可以让 K8s 部署容器应用,也是会发到 api。

Docker 的 容器 == K8s 的 Pod,但Pod可以包括多个容器

部署

Worker

  1. 部署kubelet、kubectl(但一般不用装在worker,用户用来交互的)、kubeadm(用来快速搭建集群的工具),Docker
  2. kubeadm join:加入集群,会部署 kube-proxy。

Master

  1. 部署kubelet、kubectl、kubeadm,Docker
  2. kubeadm init:初始化主节点,将该节点变成主节点,会自动部署 scheduler、kube-proxy、api、etcd、c-m;

步骤

所有步骤都要装 docker 本体,都略过,记得配置加速。

安装 kubeadm

  1. 要 RHEL 或 Debian 的操作系统,服务器配置至少要 2H2G 以上。
  2. 设置防火墙放行规则,主机名和MAC地址不可重复,允许 iptables 检查桥接流量。

    cat <<EOF | tee /etc/modules-load.d/k8s.conf 
    br_netfilter
    EOF
    
    cat <<EOF | tee /etc/sysctl.d/k8s.conf
    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    EOF
    sysctl --system
  3. 设置主机名(hostnamectl set-hostname xxxx)
  4. 永久关闭 SELlinux,关闭 Swap。

安装 kubelet、kubeadm、kubectl

# 此操作会覆盖 /etc/yum.repos.d/kubernetes.repo 中现存的所有配置
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
sudo systemctl enable --now kubelet

初始化主节点

可以提前拉取 master 节点必备的docker 镜像,kube-apiserver/kube-proxy/kube-controller-manager/coredns/etcd/pause 这些镜像。

主节点的 /etc/hosts 要提前编写好次节点的 IP 和 域名映射,次节点也要

127.0.0.2 cluster
127.0.0.3 node1
127.0.0.4 node2

然后运行主节点初始

kubeadm init \
--apiserver-advertise-address=主节点IP \
--control-plane-endpoint=主节点的主机名 \
--image-respsitory k8s的镜像远程地址 \
--kubernetes-version v1.20.9 \
--service-cidr= 10.96.0.0/16 \
--pod-network-cidr=192.168.0.0/16

所有网络范围不重叠,后面四个参数可以不用写

初始化完毕后,下面有 kubeadm join 的命令,可以让其他从从节点加入到主节点中,从而建立成董事会(Master 集群);再下面是加入工作节点。

kubectl get nodes 可以查看主节点的状态。

部署 calico 插件

部署网络组件 calico

curl https://docs.projectcalico.org/manifests/calico.yaml -O
kubectl apply -f calico.yaml

相关命令

#查看集群所有节点,相当于查看ansible有哪些在线资产,只有 master 才能执行
kubectl get nodes 

#根据配置文件,给集群创造资源
kubectl apply -f xxx.yaml

#查看集群部署了哪些应用?相当于 docker ps -a
kubectl get pods -A
NAMESPACE    NAME    READY    STATUS    RESTARTS    AGE    
- RESTARTS 是自我修复,自动重启次数
- AGE 最后启动时间
- NAMESPACE 命名空间

#如果worker节点不幸重启了,可能要手动开启 systemctl start kubelet 和 docker

加入 Worker 节点

记录主节点初始化完毕的命令,给需要当 Worker 的主机执行,加入过程也要初始化组件,所以 kubectl get pod -A 可以看初始化进展。完毕后,kubectl get nodes能看到子节点已经 Ready 了!记得关掉主节点的防火墙哦

令牌机制

上面说的初始化命令,其实是只有24小时时效的,如果后面要加的话新节点,执行这条命令

kubeadm token create --print-join-command

部署 Dashboard

部署可视化界面,方便管理

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml
kubectl get all -n kubernetes-dashboard

# 放开访问端口
kubectl edit svc kubernetes-dashboard -n kubernetes-dashboard
把 type:ClusterIP 改成 NodePort

# 找到仪表盘的外网端口,会看到 443:XXXXX/TCP,XXXXX就是外网端口
kubectl get svc -A | grep kubernetes-dashboard

访问了,要获取令牌,具体步骤看
【云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps】 【精准空降到 11:20】 https://www.bilibili.com/video/BV13Q4y1C7hS/?p=40&share_source=copy_web&vd_source=e61c16155fc4a4d0e1d899421ac8157f&t=680

实战

资源创建方式

我们可以用命令行或 YAML 的方式建立资源,YAML 可以理解成 ansible 的playbook 或 docker 的 dockerfile

Namespace

命名空间,用来隔离资源,不隔离网络。如何理解呢,就一个生产环境,上线实际业务的应用我们就划分成生产区;在测试环境,就划分成 测试区。我们就当应用分类就行了。

Namespacenode1node2node3
prod应用A应用A-2应用a-3
devAPP-DEV-1APP-DEV-2APP-DEV-3

命令行模式

  • kubectl get ns: 获取集群的命名空间;
  • kubectl delete ns xxx:删除 xxx 的命名空间,但是要谨慎删除,会把 xxx 名下的资源都会被删除掉;
  • kubectl create ns xxxx:创建 xxxx 的命名空间;
  • kubectl get pod -n xxx:查看 xxx 命名空间中的 Pod

YAML 模式,Kind 代表模式

apiVersion: v1
kind: Namespace
metadata:
    name: xxxx

创建完毕后,也要 kubectl apply -f xxx.yaml 才能生效;kubectl delete -f xxxx.yaml 会删除掉 xxxx 的命名空间

Pod

Pod 是一组容器, Pod 是K8S的最小单位。Pod 封装了 Docker 容器,允许多个容器一起运行在 Pod;

kubectl get pod -AREADY , 1/2 中的 1 代表 Pod 中有多少个应用在运行中,2 代表这 Pod 总共有多少个应用。

注意,我们只能在集群内访问 Pod 的 IP,如果要暴露公网,就需要用 Service 组件来暴露公网。我们 Pod 能实现互相访问是靠 calico

单容器

命令行模式,创建一个 nginx 容器,K8S会给容器分配一个新IP。

kubectl run mynginx --image=nginx

# 查看default名称空间的 Pod,owide参数可以显示完整一点的信息
kubectl get pod (-owide)

# 查看Pod nginx 的创建进度或问题
kubectl describe pod mynginx
## 我们主要看 Events,可以查看容器创造过程,定位问题。

# 删除 Pod
kubectl delete pod mynginx

#可以查看 Pod 的运行日志,跟 docker 的logs 差不多
kubectl logs mynginx

# 进入 mynginx 的控制台
kubectl exec -it mynginx -- /bin/bash
> root@mynginx:/# 

YAML 模式,这是单 Pod 的创建方式

apiVersion: v1
kind: Pod
metadta:
    label:
        run: mynginx
    name: mynginx
#    namespace: xxx  在XXX区创建
spec:
    containers:
    - image: nginx
      name: myningx

多容器

如果我要在 YAML 模式,建立一个多容器的 Pod 的话。他们共享网络空间与存储。

apiVersion: v1
kind: Pod
metadta:
    label:
        run: myapp
    name: myapp
#    namespace: xxx  在XXX区创建
spec:
    containers:
    - image: nginx
      name: myningx
    - image: tomcat:8.5.68
      name: tomcat

如果我们要访问不同容器,我们访问 80 端口就是访问 nginx;我们访问 8080 就是 tomcat 的容器;只不过我们访问的 IP 是 K8s 分配的 IP 。

在这个 YAML 文件中,如果我们创建两个 nginx 容器,会出现 80 端口冲突。

Deployment

控制 Pod,让 Pod 拥有多副本,自愈、故障转移与扩缩容等能力。

跟传统部署 Pod 相比,我们用 deployment 部署 pod,不怕服务器或容器宕机,自动拉起新的 pod。死了会自动在其他机器中拉起。

创建

# 创建 mytomcat 的部署
kubectl create deployment my-dep --image=tomcat:x.x.x

# 删除上面的部署
kubectl delete deploy my-dep

# 多副本部署
# 创建 mytomcat 的部署
kubectl create deployment my-dep --image=tomcat:x.x.x --replicas=3

# 查看 deploy 状态
kubectl get deploy

扩缩容

kubectl scale deploy/my-dep --replicas=2 ,这个 2 是缩容和扩容的数值

自愈

通过 Hello 心跳包观察每个 Pod 的情况。

如果应用因为内存泄露或其他宕机,会自动重启;但如果本身应用有问题,自愈能力会不断的重启,需要检查容器本身时候有问题。

其中给一个 Worker 挂掉了,5分钟(这是阈值)后自动在其他 Worker 重新拉起 Worker 中已经 Deploy 的 Pod,可以理解成故障转移。

滚动更新/回退

滚动更新是先启动 V2 版本的 Pod,如果启动成功了,会关掉 V1 的 Pod,可以保证流量不中断。如果 V2 刚开始启动失败的话,不会影响原本 V1 的 Pod。在这个过程是起一个杀一个。

kubectl set image deployment/my-dep nginx=nginx:1.16.1 --record
# kubectl set image <资源类型> <资源名称> <容器名称>=<新镜像名称>
kubectl rollout status deployment/my-dep

如果我们要回退之前的版本的话

# 查看 Pod 的历史记录
kubectl rollout  history deployment/my-dep

# 回滚,回退到第一次
kubectl rollout undo deploy/my-dep --to-revision=1

其他工作负载模式

deployment 也是有局限性的,适用于无状态的应用;其他工作模式还有 Statefulset、Daemonset、job/cronjob 这些内容。

  • Deployment: 无状态应用部署比如微服务,提供多副本等功能。
  • StatefulSet: 有状态应用部署,比如redis,提供稳定的存储、网络等功能。死了会挂载到专属的存储中。
  • DaemonSet:守护型应用部署,比如日志收集组件,在每个机器都运行一份
  • Job/CronJob:定时任务部署,比如垃圾清理组件,可以在指定时间运行

image-20231008221009950

Service

服务,用来做服务发现功能的,与负载均衡。可以理解为 Service 是一个网站服务的入口,这个入口会自动分发到 Pod 里的内部端口。我们访问的网站的话,流量会负载到每个 Pod 的服务中。

在这个 Service 内,是以 Label 标签进行分组,Service 会根据这个 deploy 的 label 形成一组映射组。

可以用 IP 与 域名访问,建立好的 Service 的内部域名形式是 label名.命名空间.svc

# 暴露映射端口,外网访问的是 8000 端口,入口则映射到 80 端口中
kubectl expose deploy my-dep --port:8000 --target-port=80 

# 我暴露的这 IP,只能在集群内访问
kubectl expose deploy my-dep --port:8000 --target-port=80 --type=ClusterIP

# 我暴露的这 IP,可以在集群外访问(公网),但是 NodePort 的范围在 30000-32767 之间
kubectl expose deploy my-dep --port:8000 --target-port=80 --type=NodePort

# 查看 Pod 标签
kubectl get pod --show-labels

# 访问 service
curl my-dep.default.svc

# 查看 service 状态
kubectl get svc

# 删除 service
kubectl delete svc my-dep

YAML 的话是这样

apiVersion: v1
kind: Service
metadta:
    label:
        app: my-dep
    name: my-dep
spec:
    selector:
      app: my-dep
    ports:
    - port:8000
      protocol: TCP
      targetPort: 80

image-20231008223740192

在负载均衡中,一台 Pod 中断后,流量不会经过异常 Pod 的 IP,会负载到其他 Pod 上。至于怎么发现中断的,源自于服务发现。

Ingress

入口,作为集群对外服务的唯一入口,是总网关,访问对应的 service 的服务端口,相当于 Ingress → Service → Pod。就是叠一层网络入口。

Ingress 基于 Nginx,用 Nginx 控制流量出入。所有流量先访问 ingress 层,找到映射关系后会转发到对应 service,如果没找到,会返回 404.

注意的是,Ingress 不是全局负载均衡, Service 是对 Pod 负载均衡,Ingress 只是把 Service 映射对应的服务出去而已。在外网也需要对 Ingress 另外配置负载均衡才行。

# 查看 ingress 资源
kubectl get ing

# 编辑 ingress 的某个资源配置文件
kubectl edit ing <资源名> -n

域名映射

在外部访问某个域名,域名的请求会交给对应的 service 进行处理。

apiVersion: v1
kind: Ingress
metadata: 
  name: ingress-host-bar
spec:
  ingressClassName: nginx
  rules:
  - host: "hello.my.com"
    http:
      paths:
      - pathType: Prefix #前缀匹配模式
        path: "/"
        backend:
          service:
            name: hello-server  #这里是名字
            port:
              number: 8000  # 这里的意义是,访问 hello.my.com 的时候,流量会转到 hello-server 这个 service 处理
  - host: "demo.my.com"
    http:
      paths:
      - pathType: Prefix #前缀匹配模式
        path: "/nginx" # 把请求转给下面的服务,如果下面的服务一定要能处理这个路径
        backend:
          service:
            name: nginx-demo  #这里是名字
            port:
              number: 8000  # 这里的意义是,访问 demo.my.com/nginx 的时候,流量会转到 nginx-demo 这个 service 处理

路径重写

这段暂时不重要,有需要我将会查 Rewrite - Ingress-Nginx Controller (kubernetes.github.io)

流量限制

使用 nginx.ingress.kubernetes.io/limit-rps 进行限流

apiVersion: v1
kind: Ingress
metadata: 
  name: ingress-limit-rate
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "1" #1秒放一个
spec:
  ingressClassName: nginx
  rules:
  - host: "ha.my.com"
    http:
      paths:
      - pathType: Exact #精确匹配,访问 ha.my.com 生效
        path: "/"
        backend:
          service:
            name: hello-server  #这里是名字
            port:
              number: 8000 

存储

在上面的搭建中,如果发生了节点故障,在其他 Worker 的主机就算重新拉起新的容器,也会因为另外的 Worker 没有原 Worker 的数据,导致数据缺失或不对等。

使用 NFS 作为存储

NFS 不是主从同步,只是共享存储,相当于映射。一般生产环境不会用 NFS 作为存储,大部分都是用分布式存储

部署
# 所有机器都要安装
yum install -y nfs-utils

主节点

# 暴露这个目录,所有人都能安全读写这个 /nfs/data 这个目录
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

mkdir -p /nfs/data
systemctl enable rpcbind --now
systemctl enable nfs-server --now
# 配置生效
exportfs -r
# 查看nfs情况
exportfs

从节点

showmount -e 主节点 IP

# 建立目录准备挂载
mkdir -p /nfs/data
mount -t nfs 主节点:/nfs/data /nfs/data
# 写入测试
echo "hello world" > /nfs/data/text.txt
用原生方式进行挂载
apiVersion: v1
kind: Deployment
metadata:
  labels:
    app: nginx-pv
  name: nginx-pv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pv
    spec:
      containers:
      - image: nginx
        name: nginx
        volomeMounts:
        - name: html # 这个是挂载名,要对应下面的实际挂载的名
          mountPath: /usr/share/nginx/html
        volumes:
          - name: html
            nfs:
              server: 主节点或NFS服务器的 IP
              path: /nfs/data/nginx-pv   # 最终nginx容器的 mountPath 会映射到这里的 path 目录中

PV 与 PVC

PV: 持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置
PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷规格

PV 就是我们提前规划好,哪些卷定了多少的空间,每个节点都部署好 PV 卷后,将会形成静态供应的 PV 池;而 PVC 相当于申请书,我想要 1G 的空间。假设现在的 PV 池有 1G20G1M 的 PV ,系统将会找匹配相近条件的 PV 池,对 1G 的 PV 卷进行绑定,饭量多少选哪个盒饭。

如果 Pod 删掉,PVC 也会跟着删,最后那 1G 的 PV 卷的数据也会回收掉。

静态供应是需要提前划分,动态供应可以按需自动创建一个 PVC 要求的空间卷。

创建 PV 池
mkdir -p /nfs/data/01
mkdir -p /nfs/data/02
mkdir -p /nfs/data/03
创建 PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv01-10m
spec:
  capacity:
    storage: 10M
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/01
    server: nfs服务器IP
--- # 这个是分隔符,也可以理解为把多个 yaml 串在一起
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv02-1G
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/02
    server: nfs服务器IP
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv03-3Gi
spec:
  capacity:
    storage: 3Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs
  nfs:
    path: /nfs/data/01
    server: nfs服务器IP

我们 kubectl get persistentvolome 可以查看持久存储;

kubectk get pv 可以查看持久卷状态;

如果看到 STATUS 是 Available 的话,是表示这些卷是可用状态;Bound 表示绑定状态,可以看右侧的 Claim;Released 表示被释放了

创建 PVC 与绑定

意思是写一份申请书

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 200Mi
    storageClassName: nfs

kubectl get pvc 查看 PVC 的状态

创建 Pod 的时候,绑定我们刚创建的 PVC。

apiVersion: v1
kind: Deployment
metadata:
  labels:
    app: nginx-pv
  name: nginx-pv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pv
    spec:
      containers:
      - image: nginx
        name: nginx
        volomeMounts:
        - name: html # 这个是挂载名,要对应下面的实际挂载的名
          mountPath: /usr/share/nginx/html
        volumes:
          - name: html
            persistentVolomeClaim:
              claimName: nginx-pvc

ConfigMap

可以抽取应用的配置文件,实现自动更新。也就是说不用传统