k8s 概述

K8s 概述

1. 基础知识

1. kubernets 是什么

Kubernetes是一个轻便、可扩展开源平台,用于管理容器化应用和服务。通过Kubernetes能够进行应用的自动化部署扩缩容。在Kubernetes中,会将组成应用的容器组合成一个逻辑单元以更易管理和发现。Kubernetes积累了作为Google生产环境运行工作负载15年的经验,并吸收了来自于社区的最佳想法和实践。Kubernetes经过这几年的快速发展,形成了一个大的生态环境,Google在2014年将Kubernetes作为开源项目。

2. Kubernetes的关键特性包括:

  • 自动化装箱:在不牺牲可用性的条件下,基于容器对资源的要求和约束自动部署容器。同时,为了提高利用率和节省更多资源,将关键和最佳工作量结合在一起。
  • 自愈能力:当容器失败时,会对容器进行重启;当所部署的Node节点有问题时,会对容器进行重新部署和重新调度;当容器未通过监控检查时,会关闭此容器;直到容器正常运行时,才会对外提供服务。
  • 水平扩容:通过简单的命令、用户界面或基于CPU的使用情况,能够对应用进行扩容和缩容。
  • 服务发现和负载均衡:开发者不需要使用额外的服务发现机制,就能够基于Kubernetes进行服务发现和负载均衡。
  • 自动发布和回滚:Kubernetes能够程序化的发布应用和相关的配置。如果发布有问题,Kubernetes将能够回归发生的变更。
  • 保密和配置管理:在不需要重新构建镜像的情况下,可以部署和更新保密和应用配置。
  • 存储编排:自动挂接存储系统,这些存储系统可以来自于本地、公共云提供商(例如:GCP和AWS)、网络存储(例如:NFS、iSCSI、Gluster、Ceph、Cinder和Floker等)。

2.kubernets 架构介绍

  • APISERVER:所有服务访问的统一接口
  • CrontrollerManager:维持副本期望值数目
  • Scheduler:负载介绍任务,选择合适的节点进行任务的分配
  • ETCD:键值对数据库,存储k8s集群所有重要信息(持久化)
  • kubelet:直接跟容器引擎交互实现容器的生命周期管理
  • kube-proxy:负责写入规则至 IPTABLES、IPVS 实现服务映射访问
  • COREDNS:可以为集群中SVC(交换虚拟电路)创建一个域名IP的对应关系解析
  • DASHBOARD:给 K8S 集群提供一个 B/S (浏览器/服务器模式)结构访问解析
  • INGRESS CONTROLLER:官方只能实现四层代理,INGRESS 可以实现七层代理

3. Pod 介绍

自主式 pod:

  • 自主式就是没有控制器管理的 pod,而 pod 就是多个容器的集合。
  • 这些容器的网络【容器开放的端口必须一致】和存储卷都和 pause 容器共享。
  • 在开启一个pod的时候,必然会开启 pause。

控制器管理的 pod:

  • RC 用来确保应用容器的副本数量始终和用户定义的数量一致。
  • RSRC 基础上,支持集合式的 selector(用标签批量操作pod)
  • deployment:管理多个 RS 然后再创建 pod ,并且可以创建新版本的RS,当新的 RS 创建之后,会停用老版本的RS而不是删除。
  • HPA:仅适用于DeploymentRS ,在V1 版本中仅支持根据Pod 的CPU 利用率扩所容,比如设置当 pod 的 cpu 利用率达到 80% 时就扩容新的 pod。
  • StatefulSet:是为了解决有状态服务的问题,比如数据库这种和用户交互的服务。
  • DaemonSet :当有Node 加入集群时,也会为他们新增一个Pod 。应用场景为:运行集群存储daemon、日志收集、服务监控
  • Job :负责批处理任务,保证批处理任务的一个或多个Pod 成功结束,即定时任务

4. 网络通讯方式

综述:

​ Kubernetes 的网络模型假定了所有Pod 都在一个可以直接连通的扁平的网络空间中【都在同一个网段,彼此可以互相ping通】,这在GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes 假定这个网络已经存在。而在私有云里搭建Kubernetes 集群,我们需要自己实现这个网络假设,将不同节点上的Docker 容器之间的互相访问先打通,然后运行Kubernete。

不同 pod 同一主机之间的通讯:

​ Pod1 与Pod2 在同一台机器,由Docker0 网桥直接转发请求至Pod2。

不同 pod 不同主机之间的通讯:

Pod的地址是与 docker0 在同一个网段的,但 docker0 网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9LrBpAl-1653055923912)(D:\云计算工坊\图片\K8S\Snipaste_2021-05-25_10-18-09.png)]

不同层级的通讯:

如搭建 LAMP 和 Nginx 时,Nginx 要反向代理 Apache 集群,就不是直接访问 Apache 集群,而是通过访问 service,再访问 Nginx。外网访问也是如此。

2. K8S 集群部署

  • 环境准备

    172.16.1.142     -master         # 主节点
    172.16.1.143	  -n001          # 从节点 01
    172.16.1.144     -n002				 # 从节点 02
    
  • 系统优化

    # 安装依赖包
    yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wgetvimnet-tools git
    
    # 设置防火墙为 Iptables 并设置空规则
    systemctl  stop firewalld  &&  systemctl  disable firewalldyum -y install iptables-services  &&  systemctl  start iptables  &&  systemctl  enable iptables&&  iptables -F  &&  service iptables save
    
    # 关闭 Selinux
    swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab  # 禁用 sawpoff 
    setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
    
    # 调整内核参数,对于 K8S
    cat > kubernetes.conf < /etc/systemd/journald.conf.d/99-prophet.conf <
  • 安装 K8S

    # kube-proxy开启ipvs的前置条件
    modprobe br_netfilter
    cat > /etc/sysconfig/modules/ipvs.modules < /etc/yum.repos.d/kubernetes.repo
    [kubernetes]
    name=Kubernetes
    baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
    enabled=1
    gpgcheck=0
    repo_gpgcheck=0
    gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
    http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
    EOF
    yum -y  install  kubeadm-1.15.1 kubectl-1.15.1 kubelet-1.15.1
    systemctl enable kubelet.service
    
    # 配置 kubeadm 镜像文件
    	# 将 kubeadm-basic.images.tar.gz 文件传到 linux 主机中
    tar -zxvf kubeadm-basic.images.tar.gz 
     vim load.sh
     ------------------------------------------------------
     #!/bin/bash
    ls /usr/local/src/kubeadm-basic.images > /tmp/images.txt
    cd /usr/local/src/kubeadm-basic.images
    for i in $(cat /tmp/images.txt);do
    	docker load -i $i
    done
    -------------------------------------------------------
    sh load.sh && cd ~
    ###############################   以上无论主从都配    ###############################
    ###############################   以下只有主节点配    ###############################
    kubeadm config print init-defaults > kubeadm-config.yaml
    vim kubeadm-config.yaml   # 配置以下内容,有的就改没有的就添加
    -------------------------------------------------------
    localAPIEndpoint:
      advertiseAddress: 192.168.80.142
    kubernetesVersion: v1.15.1
    networking:
      dnsDomain: cluster.local
      podSubnet: "10.244.0.0/16"
      serviceSubnet: 10.96.0.0/12
    scheduler: {}
    ---   
    apiVersion: kubeproxy.config.k8s.io/v1alpha1    
    kind: KubeProxyConfiguration    
    featureGates:      
            SupportIPVSProxyMode: true    
    mode: ipvs
    -------------------------------------------------------
    kubeadm init --config=kubeadm-config.yaml --experimental-upload-certs | tee kubeadm-init.log
    cat kubeadm-init.log  # 根据 To start using your cluster, you need to run the following as a regular user: 下面的内容进行配置
    mkdir -p $HOME/.kube
    cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    chown $(id -u):$(id -g) $HOME/.kube/config
    kubectl get node    # 查看有没有节点,判断是否成功
    
    # 部署网络
    mkdir install-k8s/
    mv kubeadm-init.log kubeadm-config.yaml install-k8s/
    cd install-k8s/
    mkdir core
    mv * core/
    mkdir plugin
    cd plugin/
    mkdir flanne
    	# 将 kube-flannel.yml 文件传到该目录下
    kubectl create -f kube-flannel.yml
    kubectl get pod -n kube-system # 查看有没有 flannel
    kubectl get node    # 是不是 ready
    ###############################    从节点配置    ###############################
    kubeadm join 192.168.80.142:6443 --token abcdef.0123456789abcdef     --discovery-token-ca-cert-hash sha256:8bfee48fe1560b64828d3e375fb9944a460933e9057cf60bd5b7ede41437ea45   
    ## 以上两个都配
    

3. 资源清单

1. K8S 中的资源

综述:

​ K8s中所有的内容都抽象为资源,资源实例化之后,叫做对象;【自我理解:感觉就和 Java 中的类、模块差不多,需要时直接调用相应的需求即可。】

资源的分类:

(一)名称空间级别

  • 工作负载型资源:Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob
  • 服务发现及负载均衡型资源: Service、Ingress
  • 配置与存储型资源:Volume(存储卷)、CSI
  • 特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

(二)集群级别的资源

  • Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding

(三)元数据型资源

  • HPA、PodTemplate、LimitRange

2. K8S 中的常见字段介绍

(一)必须存在的字段

字段名 字段类型 说明
apiVersion string[^2] 指定 api 的版本,目前是 v1,可以用 **kubectl apiversions **查询
kind string yml 文件中定义的资源类型是什么,比如 pod
metadata object[^3] 元数据对象
metadata.name string 元数据对象的名字,比如定义 pod 的名字
metadata.namespace string 元数据对象的命名空间,由我们自己定义
metadata.labels object 元数据对象的标签
metadata.labels.version string 元数据对象的标签的版本
spec object 详细定义对象
spec.containers[] object 定义容器某些属性
spec.containers[].name string 定义容器的名字
spec.containers[].image string 定义容器使用那个镜像

3. 简单 pod 的定义

(一)定义 pod

mkdir /kubeyml
vim /kubeyml/pod001.yml
------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:						# 大前提
name: myapp-pod
labels:
app: myapp
version: v1
spec:							# 小前提
containers:
  - name: app
    image: 192.168.80.141:5000/codefun/myapp:v1
  - name: test
    image: 192.168.80.141:5000/codefun/myapp:v1
------------------------------------------------------------

(二)启动和排查错误

`kubectl` apply -f pod001.yml    # 将 yml 文件生成相应的资源

`kubectl` get pod				  # 查看 pod 是否建立成功
# NAME        READY      STATUS           RESTARTS      AGE
# myapp-pod     1/2     CrashLoopBackOff      8          4h28m
# pod 名    启动数/总数		状态				 重启次数		创建了多长时间

kubectl describe pod myapp-pod    # 查看指定 pod 的详细信息,比如 pod 的详细信息,容器的详细信息,容器的执行事件,存储卷信息

kubectl logs -p myapp-pod -c test  # 查看指定 pod 下的指定容器的日志

kubectl delete pod myapp-pod     # 删除指定pod

4. 容器的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pi9s6Zxt-1653055923913)(D:\云计算工坊\图片\K8S\容器的生命周期.png)]

1. init C 的详解与模板应用

(一)Init 容器介绍

特点:

  • init容器总是运行到成功完成为止
  • 每个init容器都必须在下一个Init容器启动之前成功完成
  • 如果pod重启,所有init c容器必须重新执行
  • init 容器具有应用容器的所有字段,除了就绪检测和生存检测。
  • pod中的每个appinit容器的名称必须唯一

作用:

  • 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的【比如在主容器启动之前,我们需要梳理某些文件或者动作,但是这些动作或文件可能只运行一次,为了减少主容器的工作量】
  • 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。【也就是一部分是构建代码init c,而另一部分运行代码 主容器】
  • Init容器使用LinuxNamespace它们能够具有访问Secret的权限,而应用程序容器则不能。
  • 若 pod 内有依赖性强的容器,则可以使用init c 来指定容器启动的顺序,从而保证 pod 的运行状态

总结

就是充分利用 init cmain c相互剥离但又能控制main c 的特点,以及优先于main c 执行的特点。

(二) 模板应用

vim /kubeyml/pod002.yml
------------------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:  
app: myapp
spec: 
containers:  
- name: myapp-container  
  image: busybox    
  command: ['sh','-c','echo The app is running! && sleep 3600'] 
initContainers:  
- name: init-myservice  # 检测 myservice 的 DNS,若不成功就返回 waiting for myservice 然后睡两秒再次检测。
  image: busybox   
  command: ['sh','-c','until nslookup myservice; do echo waiting for myservice; sleep 2;done;'] 
- name: init-mydb   
  image: busybox   
  command: ['sh','-c','until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
------------------------------------------------------------------------------------------------------
`kubectl` apply -f pod002.yml

kubectl get pod
# NAME        READY   STATUS     RESTARTS   AGE
# myapp-pod   0/1     Init:0/2   0          10m

cat /kubeyml/svs001.yml 
------------------------------------------------------------------------------------------------------
kind: Service
apiVersion: v1
metadata:  
name: myservice
spec:  
ports:    
  - protocol: TCP      
    port: 80      
    targetPort: 9376
------------------------------------------------------------------------------------------------------
`kubectl` apply -f svs001.yml
kubectl get pod
# NAME        READY   STATUS     RESTARTS   AGE
# myapp-pod   0/1     Init:1/2   0          10m

cat /kubeyml/svc002.yml 
-----------------------------------------------------------------------------------------------------
kind: Service
apiVersion: v1
metadata:  
name: mydb
spec:  
ports:
  - protocol: TCP
    port: 80
    targetPort: 9377
-----------------------------------------------------------------------------------------------------
`kubectl` apply -f svc002.yml
kubectl get pod
# NAME        READY   STATUS    RESTARTS   AGE
# myapp-pod   1/1     Running   0          13m

总结一下

  • 使用 initContainers创建 init c 容器,并执行检测 DNS 解析命令
  • 当一个 init c 容器执行完后就会死亡,并且执行下一个 init c 容器

2. 探针的使用

(一)什么是探针

  • 探针是由kubelet对容器执行的定期诊断。要执行诊断,kubelet调用由容器实现的Handler

(二)探针的三种处理程序

  • ExecAction:在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功。

  • TCPSocketAction:对指定端口上的容器的 IP地址进行TCP检查。如果端口打开,则诊断被认为是成功的。

  • HTTPGetAction:对指定的端口和路径上的容器的IP地址执行HTTP Get请求。如果响应码大于等于200且小于400,则成功

  • 探测结果:

  • 成功:容器通过了诊断。

  • 失败:容器未通过诊断。

  • 未知:诊断失败,因此不会采取任何行动

(三)探测方式

  • livenessProbe:指示容器是否正在运行。如果存活探测失败,则kubelet会杀死容器,并且容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为Success
  • readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与Pod匹配的所有Service的端点中删除该Pod的IP地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success

(四)readinessProbe探测实例

cat /kubeyml/pod003.yml 
---------------------------------------
apiVersion: v1
kind: Pod
metadata:  
name: readiness-httpget-pod
namespace: default
spec:
containers:
- name: readiness-httpget-container
  image: wangyanglinux/myapp:v1
  imagePullPolicy: IfNotPresent           # 镜像拉取规则,如果本地有就不向远程拉取
  readinessProbe:                    # 就绪检测
    httpGet:                       # 检测方案:httpget 方案
      port: 80
      path: /index1.html               # 检测的路径本来就没有
    initialDelaySeconds: 1              # 初始化的延迟
    periodSeconds: 3                  # 重试的时间
---------------------------------------
`kubectl` get pod
# NAME              READY                 STATUS    RESTARTS      AGE
# readiness-httpget-pod   0/1 # 没有真正的开启        Running   1          86m

kubectl exec -it readiness-httpget-pod -- /bin/sh   ###  进入容器的命令,还可跟 -c 命令
/ # cd /usr/share/nginx/html/
/usr/share/nginx/html # ls
50x.html    index.html
/usr/share/nginx/html # echo "hello world" > index1.html
/usr/share/nginx/html # exit
[root@master ~]# kubectl get pod
# NAME                READY   STATUS    RESTARTS       AGE
# readiness-httpget-pod   1/1##     Running   1            91m

(五)生存检测案例

cat pod004.yml ## ExecAction 类型
-----------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: live-exec-pod
namespace: default
spec:
containers:
- name: live-exec-con
  image: busybox
  imagePullPolicy: IfNotPresent
  command: ["/bin/sh","-c","touch /tmp/live;sleep 10;rm -rf /tmp/live;sleep 3600"]   # 创建一个文件
  livenessProbe:
    exec:
      command: ["test","-e","/tmp/live"]    # 检测是否有该文件
    initialDelaySeconds: 1
    periodSeconds: 3
------------------------------------------------------------------------------------------
`kubectl` get pod -w   # 获得 pod 的持续性状态
# AME            READY   STATUS    RESTARTS   AGE
# live-exec-pod   1/1     Running   0          5s
# live-exec-pod   1/1     Running   1          49s   # 在这里重启了一次

cat /kubeyml/pod005.yml ## HTTPGetAction 类型
------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: live-httpget-pod
namespace: default
spec:
containers:
  - name: live-httpget-con
    image: 192.168.80.141:5000/codefun/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 8080    # 对外开放 80 端口
    livenessProbe:
      httpGet:
        port: http
        path: /index.html   # 使用 http 协议进行 get 请求,并且访问 80 端口
      initialDelaySeconds: 1
      periodSeconds: 3
      timeoutSeconds: 10
------------------------------------------------------------------------------------------
`kubectl` exec -it live-httpget-pod --  rm -rf /usr/share/nginx/html/index.html
`kubectl` get pod
# NAME            READY   STATUS    RESTARTS   AGE
# live-httpget-pod   1/1     Running   1       2m45s   # 这里重启了一次,因为检测不到 index.html

cat /kubeyml/pod006.yml
------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: live-tcp-pod
namespace: default
spec:
containers:
- name: live-tcp-con
  image: 192.168.80.141:5000/codefun/myapp:v1
  livenessProbe:
    initialDelaySeconds: 5
    timeoutSeconds: 1
    tcpSocket:
      port: 8080
    periodSeconds: 3
------------------------------------------------------------------------------------------
# kubectl get pod
# NAME        READY   STATUS          RESTARTS   AGE
# live-tcp-pod   0/1    CrashLoopBackOff   3        57s    # 检测到 8080 没有监听,所以一直重启

3. 启动程序和停止程序的定义

(一) 案例实施

cat /kubeyml/pod007.yml 
------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-pod
spec:
containers:
- name: lifecycle-con
  image: 192.168.80.141:5000/codefun/myapp:v1
  lifecycle:
    postStart:
      exec:
        command: ["/bin/sh","-c","echo hello world > /usr/share/message"]
    preStop:
      exec:
        command: ["/bin/sh","-c","echo hello bad > /usr/share/message"]
------------------------------------------------------------------------------------------
`kubectl` apply -f /kubeyml/pod007.yml # 启动时向 /usr/share/message 中插入 hello world
pod/lifecycle-pod created
`kubectl` exec -it lifecycle-pod -- cat /usr/share/message
hello world

4. pod 的五种状态

  • 挂起(Pending):Pod已被Kubernetes系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间,这可能需要花点时间
  • 运行中(Running):该Pod已经绑定到了一个节点上,Pod中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态
  • 成功(Succeeded):Pod中的所有容器都被成功终止,并且不会再重启
  • 失败(Failed):Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止
  • 未知(Unknown):因为某些原因无法取得Pod的状态,通常是因为与Pod所在主机通信失败

4. 创建控制器

控制器:

​ 用来控制和管理 pod 的工具

1. Deployment 控制器

1. 使用环境

  • 确保容器应用的副本数始终保持在用户定义的副本数
  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

2. 控制器基础案例

(一) 部署 RS

cat /kubeyml/rs001.yml
--------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1     # 定义 api 版本,控制器好像都是这个版本
kind: ReplicaSet              # 创建的类型
metadata:							 # 大前提
name: frontend
spec:								 # 小前提,细节
replicas: 3
selector:							 # 选择器:即定义选择那些 pod 加入 RS
matchLabels:
tier: frontend          # 定义一个标签,有此标签的 pod 加入 RS
template:							 # 设置模板 pod
metadata:
labels:
  tier: frontend        # 定义标签
spec:
containers:
   - name: rs-nginx
     image: 192.168.80.141:5000/codefun/myapp:v1
     env:
     - name: GET_HOST_FROM
       value: dns
     ports:
     - containerPort: 80
--------------------------------------------------------------------------------------------
`kubectl` label pod frontend-7w77k tier=codefun --overwrite=True
pod/frontend-7w77k labeled

`kubectl` get pod --show-labels    # 又多了一个 pod 
NAME             READY   STATUS    RESTARTS   AGE     LABELS
frontend-72nzv   1/1     Running   0          5s      tier=frontend
frontend-7w77k   1/1     Running   0          4h14m   tier=codefun
frontend-mhfwb   1/1     Running   0          4h14m   tier=frontend
frontend-sq98s   1/1     Running   0          4h14m   tier=frontend

(二)部署 Deployment

cat /kubeyml/dep001.yml 
--------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1
kind: Deployment				# 就 kind 值不一样,其他基本相同
metadata:
name: nginx-dep
spec:
replicas: 3
selector:						# 创建没有什么意义
 matchLabels:
   app: nginx
template:
 metadata:
   labels:
     app: nginx
 spec:
   containers:
      - name: nginx-con
        image: 192.168.80.141:5000/codefun/myapp:v1
        ports:
        - containerPort: 80
--------------------------------------------------------------------------------------------
`kubectl` create -f /kubeyml/dep001.yml --record## --record参数可以记录命令,我们可以很方便的查看每次 revision 的变化
`kubectl` get deployment
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
nginx-dep   3/3     3            3           13m

(三)Deployment 控制器的应用

(1)扩容副本

`kubectl` scale deployment deployment-name --replicas 扩容数   # 语法
## 实例:
`kubectl` scale deployment nginx-dep --replicas 5          # 将副本数扩容为 5 个
deployment.extensions/nginx-dep scaled
kubectl get deployment                            # 只是在原来的基础上扩容,而不是新增 rs
NAME  DESIRED  CURRENT   READY   AGE
nginx 5     5         5       21m

(2)更新镜像

`kubectl` set image deployment/deployment-name container-name=image-name  # 语法
## 示例:
`kubectl` set image deployment/nginx-dep nginx-con=wangyanglinux/myapp:v2  
`kubectl` get rs       # 新增一个 RS, 将 pod 滚动更新到 新的 RS
NAME                   DESIRED   CURRENT   READY   AGE
nginx-dep-6f58ddf7d9   0         0         0       28m
nginx-dep-768b498b9f   5         5         5       93s

(3)回滚 RS

### 基础命令
`kubectl` rollout undo deployment/deployment-name  # 语法
## 示例:
`kubectl` rollout undo deployment/nginx-dep  # 默认回滚到上一个 rs
deployment.extensions/nginx-dep rolled back
`kubectl` get rs
NAME                   DESIRED   CURRENT   READY   AGE
nginx-dep-6f58ddf7d9   5         5         4       36m
nginx-dep-768b498b9f   0         0         0       8m42s

查看回滚状态

kubectl rollout status deployment/devloyment-name

查看历史版本的 rs

kubectl rollout history deployment/devloyment-name
deployment.extensions/nginx-dep
REVISION CHANGE-CAUSE
2 kubectl apply --filename=/kubeyml/dep001.yml --record=true
4 kubectl apply --filename=/kubeyml/dep001.yml --record=true
5 kubectl apply --filename=/kubeyml/dep001.yml --record=true

回滚到指定的版本

kubectl rollout undo deployment/deployment --to-revision=REVISION

暂停deployment更新

kubectl rollout pause deployment/deployment-name

回滚策略

如果创建了10个 v1 版本的 deployment,当创建到第三个的时候,执行 v1 版本 改为 v2 版本的操作,这时就不会再创建 v1 版本的,而是将 v1 的三个杀死,创建 v2 版本的。

deployment 更新策略

· Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)
· eployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )

设置历史版本的个数

您可以通过设置.spec.revisonHistoryLimit项来指定 deployment 最多保留多少 revision 历史记录。默认的会保留所有的 revision;如果将该项设置为0,Deployment 就不允许回退了.

实现:

vim /kubeyml/dep001.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-dep
spec:
replicas: 3

revisionHistoryLimit: 1 # 保留两个


2. DaemonSet 控制器

1. 使用环境

DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。

  • 运行集群存储
  • 运行日志收集
  • 运行监控

2. 控制器基础案例

cat /kubeyml/dae.yml 
--------------------------------------------------------------------------------------------
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: dae
  labels:
    app: dae-v1           # 一样
spec:
  selector:
    matchLabels:
      name: dae-v1        # 一样
  template:
    metadata:
      labels:
        name: dae-v1      # 一样
    spec:
      containers:
      - name: dae-v1
        image: 192.168.80.141:5000/codefun/myapp:v1 
--------------------------------------------------------------------------------------------
 `kubectl` create -f /kubeyml/dae.yml

3. Job 和 Cronjob 控制器

1. 使用环境

Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束

Cron Job 基于时间的 Job,即:

  • 在给定时间点只运行一次
  • 周期性地在给定时间点运行

2. 控制器的基础案例

(一) job

cat /kubeyml/job001.yml 
--------------------------------------------------------------------------------------------
apiVersion: batch/v1
kind: Job
metadata:
name: demo-job
spec:
parallelism: 3       # 设置并行 pod 的数目,默认为一,增大可以增加执行效率
template:
metadata:
name: demo-job
spec:
containers:
    - name: demo-job
      image: busybox
      command: ["echo","hello world Job!"]
    restartPolicy: OnFailure      # 失败了就重启 pod ,Never 从不重启
--------------------------------------------------------------------------------------------
`kubectl` get pod   # 当 status 为 Completed 时为执行成功
NAME             READY   STATUS      RESTARTS   AGE
demo-job-5zwm9   0/1     Completed   0          3m2s

`kubectl` get job
NAME       COMPLETIONS   DURATION   AGE
demo-job   3/1 of 3      36s        4m11s

(二)Cronjob

cat /kubeyml/cro001.yml 
--------------------------------------------------------------------------------------------
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"    # 每秒执行一次
jobTemplate:
spec:
template:
  spec:
    containers:
        - name: hello
          image: busybox
          args:
          - /bin/sh
          - -c
          - date;echo hello world CronJob!
        restartPolicy: OnFailure
--------------------------------------------------------------------------------------------

`kubectl` get pod
NAME                     READY   STATUS      RESTARTS   AGE
hello-1622117340-pgtcj   0/1     Completed   0          92s
hello-1622117400-xx6wg   0/1     Completed   0          32s
`kubectl` get job    # cronjob 也是 job 的一种,只不过加了定时任务而已
NAME               COMPLETIONS   DURATION   AGE
hello-1622117340   1/1           18s        2m7s
hello-1622117400   1/1           19s        67s
hello-1622117460   0/1           7s         7s
`kubectl` get cronjob
NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        28s             6m57s

ronjob 各参数详解

  • spec.schedule:调度,必需字段,指定任务运行周期,格式同 Cron

  • .spec.jobTemplate:Job 模板,必需字段,指定需要运行的任务,格式同 Job

  • .spec.startingDeadlineSeconds:启动 Job 的期限(秒级别)如果没有指定,则没有期限.

  • spec.concurrencyPolicy:并发策略,该字段也是可选的。只允许指定下面策略中的一种:

  • Allow(默认):允许并发运行

  • JobForbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个

  • Replace:取消当前正在运行的 Job,用一个新的来替换。

  • .spec.suspend:挂起,该字段也是可选的。如果设置为true,后续所有执行都会被挂起。它对已经开始执行的 Job 不起作用。默认值为false。

  • .spec.successfulJobsHistoryLimit和.spec.failedJobsHistoryLimit:历史限制,是可选的字段。它们指定了可以保留多少完成和失败的 Job。默认情况下,它们分别设置为3和1。设置限制的值为0,相关类型的 Job 完成后将不会被保留

5. service 介绍

1. 基础概念

1. service 的定义

一种可以访问pod集群的策略 —— 通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector

  • service 的限制:只提供 4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kRY5cCnE-1653055923915)(D:\云计算工坊\图片\K8S\service定义.png)]

2. service 的分类

ClusterIp:默认类型,自动分配一个仅集群内部可以访问的虚拟 IP

NodePort:在ClusterIP基础上为Service 在每台机器上绑定一个端口,这样就可以通过主机IP:NodePort来访问该服务

LoadBalancer:在NodePort 的基础上,借助cloud provider创建一个外部负载均衡器,并将请求转发到NodePort

NodePortExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持

监听服务和端点是由apiserver负责的,通过 kube-proxy 去监控对应的 pod ,并提取相应的信息,将其写入到 iptables,当客户端想要访问 service 的时候,其实访问的的是 iptables 规则,然后导向到后端的 pod 的地址信息

客户端访问到我们的节点是由 iptables 实现的
iptables 的规则是由 kube-proxy 写入的,apiserver通过监控 kube-proxy 进行 服务和端点的发现 ,kube-proxy 会通过 pod 的标签去判断这个端点是否写入到端点集合里去。

2. 基础案例实践

(一)ClusterIP 和 NodePort

cat /kubeyml/svc003.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
name: svc003
namespace: default
spec:
type: NodePort
selector:
app: nginx
ports:
- name: http
  port: 80
  targetPort: 80
--------------------------------------------------------------------------------------------
`kubectl` apply -f /kubeyml/svc003.yml
`kubectl` get svc
# NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
# svc003       NodePort    10.108.138.100           80:32181/TCP   18m

(二)externalName

kind: Service
apiVersion: v1
metadata: 
name: my-service-1 
namespace: default
spec:
type: ExternalName  
externalName: hub.atguigu.com

3. nginx-ingress

(一)service 只能实现四层暴露,而nginx-ingress 可以实现七层暴露

(二)部署过程

kubectl apply -f /root/install-k8s/plugin/ingress/mandatory.yml
kubectl apply -f /root/install-k8s/plugin/ingress/service-nodePort.yml

(三)简单案例的部署

cat /kubeyml/ingress/dep-demo.yml
--------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-dep
spec:
replicas: 2
template:
metadata:
labels:
name: nginx
spec:
containers:
  - name: nginx
    image: wangyanglinux/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
      - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc                  # 与 ingress 的唯一标识
spec:
ports:
- port: 80
  targetPort: 80
  protocol: TCP
selector:
name: nginx
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-demo
spec:
rules:
- host: www1.codefun.com
  http:
    paths:
    - path: /
      backend:
        serviceName: nginx-svc    # 管理那个 service
        servicePort: 80
--------------------------------------------------------------------------------------------
kubectl apply -f /kubeyml/ingress/dep-demo.yml
curl www1.codefun.com:30051   # 发现可以访问该域名

(四)实现 https

# 创建 https 密钥
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
kubectl create secret tls tls-sercret --key tls.key --cert tls.crt
# 编写 yml 文件
vim /kubeyml/ingress/https_ingress.yml
--------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ing001
spec:
tls:      # 注意该字段下与之前建立的密钥相对应
  - hosts:
    - www.code666.com
    secretName: tls-secret
rules:
  - host: www.code666.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-svc001
          servicePort: 80
--------------------------------------------------------------------------------------------
# 在 网页上访问 https://www.code666.com:32458,注意此时映射的端口为 443!

(五)实现用户认证

# 创建密码文件
yum install -y httpd
mkdir htpasswd && cd htpasswd
htpasswd -c auth codefun 
kubectl create secret generic basic-auth --from-file=auth
# 编写 yml 文件
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-dev004
spec:
replicas: 2
template:
 metadata:
   labels:
     name: nginx004
 spec:
   containers:
        - name: nginx004
          image: wangyanglinux/myapp:v2
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc004
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    name: nginx004
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ing004
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - codefun'
spec:
  rules:
    - host: www.codeau.com
      http:
        paths:
        - path: /
          backend:
            serviceName: nginx-svc004
            servicePort: 80
---

(六)域名重定向

cat /kubeyml/ingress/rew_sour.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: rew-sour-ing
annotations:
 nignx.ingress.kubernetes.io/rewrite-target: http://www.code.com:30051/
spec:
rules:
    - host: www.codefun.com
      http:
        paths:
        - path: /
          backend:
            serviceName: rewrite-svc02
            servicePort: 80

6. k8s 中的存储

1. 保存对象为环境变量的 configMap

(一) 特点:

  • ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象

(二)以目录为单位创建 configmap

# 创建相关目录和文件,并向文件中输入信息
mkdir /root/configmap/ && cd /root/configmap
cat game.properties 
--------------------------------------------------------------------------------------------
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=no
GoodRottensecret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=truesecret.code.lives=30
--------------------------------------------------------------------------------------------
cat ui.properties 
--------------------------------------------------------------------------------------------
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
--------------------------------------------------------------------------------------------
# 创建 configmap
`kubectl` create configmap config-dir --from-file=/root/configmap/ # config-dir 为 configmap 的名字,/root/configmap/ 是 名为 config-dir 的 configmap 保存那些资源。
# --from-file 指定的资源,以文件为键,以内容为值。

(三)以字面值为单位创建 configmap

kubectl create configmap config-word --from-literal=key1=value1 --from-literal=key2=value2
kubectl describe config-world  # 查看包括那些键值

四)使用 yml 文件来创建 configmap

vim /kubeyml/env_cm.yml
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: config-yml
namespace: default
data:
key: value
--------------------------------------------------------------------------------------------
kubectl create -f /kubeyml/env_cm.yml

(五)在 pod 中使用 configMap 中的键值

cat /kubeyml/env_pod.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
    - name: env-con
      image: wangyanglinux/myapp:v2
      command: ["/bin/sh","-c","env"]     # 打印所有的环境变量
      env:									
        - name: SPECIAL_LEVEL_KEY	 # 定义在 pod 中的 key
          valueFrom:			# 变量来自于哪里
            configMapKeyRef:
              name: game-config			# 变量的 configmap 的名字
              key: game.propreties		# 变量的 key
     envFrom:			# z直接导入整个 configmap 中的键和值
      - configMapKeyRef:
         name: config-map-name 
  restartPolicy: Never
--------------------------------------------------------------------------------------------

(六)通过数据卷插件使用 configmap

cat /kubeyml/env_vol_pod.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: env-vol-pod
spec:
containers:
    - name: env-vol-con
      image: wangyanglinux/myapp:v1
      command: ["/bin/sh","-c","sleep 360s"]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: game-config
  restartPolicy: Never
--------------------------------------------------------------------------------------------
kubectl apply -f /kubeyml/env_vol_pod.yml
 kubectl exec -it env-vol-pod -- /bin/sh
/ # cd /etc/config
/etc/config # ls      # 由此发现,将文件填入数据卷,在这个文件中,键就是文件名,键值就是文件内容
kubectl edit cm game-config   # 修改环境变量,容器也跟着改变,实现热部署

2. 加密存储的 secret

(一)secret 存在的意义

  • Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec中。Secret 可以以 Volume 或者环境变量的方式使用

(二)secret 的分类

  • ervice Account:用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的/run/secrets/kubernetes.io/serviceaccount目录中
  • Opaquebase64编码格式的Secret,用来存储密码、密钥等
  • kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息

(三)创建 Opaque 类型的 secret

  • 将 secret 导入到 volume 中

    # 获取 base64 加密的信息
    cho -n "admin" | base64
    YWRtaW4=		# 此加密信息就是个假的,很容易反解密
    echo -n "123456" | base64
    MTIzNDU2
    # 编写有关 secret 的 yml 文件
    cat /kubeyml/secrets.yml 
    --------------------------------------------------------------------------------------------
    apiVersion: v1
    kind: Secret
    metadata:
      name: mysecret
    type: Opaque
    data:
      password: MTIzNDU2
      username: YWRtaW4=
    --------------------------------------------------------------------------------------------
    # 编写使用 secret 的 pod
    cat /kubeyml/sec_vol_pod.yml 
    --------------------------------------------------------------------------------------------
    apiVersion: v1
    kind: Pod
    metadata:
      name: sec-pod
      labels:
        name: sec-pod
    spec:
      volumes:
      - name: secrets
        secret:
          secretName: mysecret     # 与上文定义的相同
      containers:
      - image: wangyanglinux/myapp:v2
        name: sec-con
        volumeMounts:
        - name: secrets
          mountPath: /etc/config
          readOnly: true
    --------------------------------------------------------------------------------------------
    
  • 将 secret 导入到环境变量中

    cat /kubeyml/sec_env_pod.yml 
    --------------------------------------------------------------------------------------------
    apiVersion: v1
    kind: Pod
    metadata:
      name: sec-env-pod
    spec:
      containers:
      - name: sec-env-con
        image: wangyanglinux/myapp:v1
        command: ["/bin/sh","-c","echo $ADMIN"]
        ports:
        - containerPort: 80
        env:
          - name: ADMIN
            valueFrom:
              secretKeyRef:    ###
                name: mysecret
                key: username
    --------------------------------------------------------------------------------------------
    
  • 拉取私有仓库免登录

    # 创建密钥
    kubectl create secret docker-registry myregistrykey --docker-server=192.168.80.141:5000 --docker-username=admin --docker-password=Gx123456 [email protected]
    # 编写 yml 文件
    cat /kubeyml/reg_pod.yml 
    --------------------------------------------------------------------------------------------
    apiVersion: v1
    kind: Pod
    metadata:
      name: reg-pod
    spec:
      containers:
      - name: reg-con
        image: 192.168.80.141:5000/codefun/nginx01:v1
      imagePullSecrets:
      - name: myregistrykey
    --------------------------------------------------------------------------------------------
    

3. 可以共享的 volume

(一)同 pod 共享存储的 emptyDir

  • 当 Pod 被分配给节点时,首先创建emptyDir卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir中的数据将被永久删除

  • 暂存空间,例如用于基于磁盘的合并排序用作长时间;计算崩溃恢复时的检查点;Web服务器容器提供数据时,保存内容管理器容器提取的文件

  • 案例示例

    cat /kubeyml/vol_pod.yml 
    --------------------------------------------------------------------------------------------
    apiVersion: v1
    kind: Pod
    metadata:
      name: vol-pod
      namespace: default
    spec:
      containers:
      - name: vol-con001
        image: wangyanglinux/myapp:v1
        volumeMounts:
        - mountPath: /cache
          name: cache-volume   # 定义相同的名字
      - name: vol-con002
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh","-c","sleep 360s"]
        volumeMounts:
        - mountPath: /test
          name: cache-volume   # 定义相同的名字
      volumes:
      - name: cache-volume   
        emptyDir: {}
    --------------------------------------------------------------------------------------------
    kubectl apply -f /kubeyml/vol_pod.yml 
    kubectl exec -it vol-pod -c vol-con001 -- /bin/sh
    / # cd cache/
    /cache # date > test.txt
    /cache # cat test.txt 
    Wed Jun  2 09:19:41 UTC 2021
    kubectl exec -it vol-pod -c vol-con002 -- /bin/sh
    / # cd test
    /test # cat test.txt 
    Wed Jun  2 09:19:41 UTC 2021
    

(二)共享到本地的 hostpath

综述

  • hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中

用途

  • 运行需要访问 Docker 内部的容器:使用/var/lib/docker的hostPath
  • 在容器中运行 cAdvisor;使用/dev/cgroups的hostPath
  • 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建,以及它应该以什么形式存在

参数总结

k

行为
path node 节点的目录路径
null 空字符串用于向后兼容,意味着在挂载 hostpath 卷之间不会执行任何检查
DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 077 ,与 kubelet 具有相同的组和所有权
Directory 给定的路径下必须存在目录
FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设为 0644,与 kubelet 具有相同的组和权限
File 给定路径下必须存在文件
Socket 给定路径下必须存在 UNIX 套接字
CharDevice 给定路径下必须存在字符设备
BlockDevice 给定的路径下必须存在块设备

案例实现

cat /kubeyml/vol_host_pod.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: vol-host-pod
spec:
containers:
  - name: vol-host-con
    image: wangyanglinux/myapp:v2
    volumeMounts:
    - name: vol-host-vol
      mountPath: /test
  volumes:
  - name: vol-host-vol
    hostPath:
      path: /data
      type: Directory
--------------------------------------------------------------------------------------------
kubectl apply -f /kubeyml/vol_host_pod.yml
kubectl exec -it vol-host-pod -- /bin/sh
/ # date > /test/test.txt
/ # cat /test/test.txt 
Thu Jun  3 00:42:28 UTC 2021
[root@n002 ~]# mkdir /data
[root@n002 ~]# cat /data/test.txt 
Thu Jun  3 00:42:28 UTC 2021

4. 持久卷

概念

  • PV 持久卷,由运维设置的存储。是集群中的资源,类似于 volume 之类的卷插件,但具有独立于 pod 的生命周期。此 API 对象包含存储实现的细节,如使用 NFS 来达到目录共享的功能。
  • PVC 持久卷声明,由开发设置的类型。类似于 pod ,pod 消耗节点资源,PVC 消耗 PV 资源。
  • 静态 PV ,集群管理员创建的 PV。
  • 动态 PV , 当管理员创建的静态 PV 都不匹配用户的 PVC 时,集群可能会自己尝试动态的为 pvc 创建卷。
  • PV 访问模式:
    • ReadWriteOnce – 单个节点读写模式挂载
    • ReadOnlyMany – 多个节点只读模式挂载
    • ReadWriteMany – 多个节点读写模式挂载
  • 状态:
    • Available – 可用的空闲资源
    • Bound – 卷已被声明绑定
    • Released– 声明已被删除
  • 回收策略:
    • Retain – 手动回收
    • Delete– 直接删除
    • Failed – 自动回收失败
  • PV 和 PVC 匹配规则
    • PVC 和 PV 的 storageClassName 必须一致
    • PVC 和 PV 的 accessModes 必须一致
    • PVC 的大小要小于或等于 PV 的大小

持久化演示说明 - NFS

# 安装 nfs 服务
yum install -y nfs-utils rpcbind
mkdir /nfs{0..3}
chown nfsnobody /nfs{0..3}
chmod 777 /nfs{0..3}
vim /etc/exports
--------------------------------------------------------------------------------------------
/nfs0 *(rw,sync,no_root_squash,no_all_squash)
/nfs1 *(rw,sync,no_root_squash,no_all_squash)
/nfs2 *(rw,sync,no_root_squash,no_all_squash)
/nfs3 *(rw,sync,no_root_squash,no_all_squash)
--------------------------------------------------------------------------------------------
systemctl restart rpcbind
systemctl restart nfs

# 编写 pv 的 yml 文件
cat /kubeyml/mode_pv.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: PersistentVolume
metadata:
name: mode-pv
spec:
capacity:
 storage: 2Gi		# 定义 PV 的容量
accessModes:		# 定义 PV 的访问模式
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle		# 定义 PV 的回收策略
  storageClassName: nfs		# 与 PVS 的唯一标识
  nfs:		# 设置持久卷类型为 NFS
    path: /nfs0    # 远程的挂载目录
    server: 192.168.80.141		# 远程挂载目录主机的 IP
--------------------------------------------------------------------------------------------
kubectl apply -f /kubeyml/mode_pv.yml

# 编写 pvc 及其他的 yml 文件
cat /kubeyml/mode_pvc.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: mode-pvc-svc
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None		# 定义 statefulSet 时,service 必须是无头服务
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mode-pvc-ss
spec:
  selector:
    matchLabels:
      app: nginx		# 与 container 互相标识
  serviceName: "mode-pvc-svc"		# 与 service 的唯一标识
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx				# 与 statefulset 互相标识
    spec:
      containers:
      - name: mode-pvc-con
        image: wangyanglinux/myapp:v2
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www  # PVC 的名字
    spec:
      accessModes: [ "ReadWriteOnce" ]		# 与 PV 的标识
      storageClassName: "nfs"		# 与 PV 的标识
      resources:
        requests:
          storage: 1Gi		# 大小
--------------------------------------------------------------------------------------------
kubectl apply -f /kubeyml/mode_pvc.yml 

# 另一种写法
cat mode_pv003.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mode-pv003
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: mode-pvoo3
  nfs:
    path: /nfs3
    server: 192.168.80.141
--------------------------------------------------------------------------------------------
cat mode_pvc001.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mode-pvc001
spec:
  storageClassName: mode-pvoo3
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 500Mi
--------------------------------------------------------------------------------------------
cat mod_pv_pod.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
  name: mode-pv-pod
spec:
  containers:
  - name: mode-pv-con
    image: wangyanglinux/myapp:v1
    ports:
    - containerPort: 80
      protocol: TCP
    volumeMounts:
    - name: mode-pv-vol
      mountPath: /test
  volumes:
    - name: mode-pv-vol
      persistentVolumeClaim:
        claimName: mode-pvc001
--------------------------------------------------------------------------------------------

statefulSet 模型

  • StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
  • 每一个pod不能被随意取代,pod名称不变,而pod IP是变化的,所以pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless servername),
  • 共享存储这里用的是 NFS

7. 集群的调度器

1. 调度说明

简介

Scheduleer 是 K8S 的调度器,主要任务是把定义的 pod 分配到集群的 node上。分配时需要考虑的问题:

  • 公平:如何保证每个节点都能被分配资源
  • 资源的高效利用:集群所有资源最大化被使用
  • 效率:能够尽快地对大批量的 pod 完成调度工作
  • 灵活:允许用户根据自己的需求控制调度逻辑

Shedeler是作为单独运行程序,启动之后会一直坚挺 API Server,获取PodSpec.NodeName为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。

调度过程

  1. 预选:首先过滤掉不满足条件的节点,称为 predicate
  2. 优选:根据节点优先级来最终决定 pod 的归属,称为priority

预选算法:

  • PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源
  • PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
  • PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突
  • PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点
  • NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读

优选算法:

  • eastRequestedPriority:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
  • BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
  • ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高

选择细节:

  • 如果在 predicate 过程中没有合适的节点,pod 会一直在pending状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序
  • 通过算法对所有的优先级项目和权重进行计算,得出最终的结果

自定义调度器

apiVersion: v1
kind: Pod
metadata:
name: annotation-second-scheduler
labels:
name: multischeduler-example
spec:  
schedulername: my-scheduler  
containers:  
  - name: name-com
    image: image-name:tag

2. 节点亲和性

软策略

​ —— 不必须匹配的策略

# 实现方法:
cat /kubeyml/affinity/pre_pod001.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: pre-pod
labels:
 app: pre-pod
spec:
containers:
  - name: pre-con
    image: wangyanglinux/myapp:v2
    imagePullPolicy: IfNotPresent
  affinity:						# 策略
    nodeAffinity:			  # 节点匹配策略
      preferredDuringSchedulingIgnoredDuringExecution:    # 软策略
      - weight: 1		# 权重
        preference:		# 软策略详情
          matchExpressions:		# 选择详请
          - key: kubernetes.io/hostname		# 选择 key 和 value 是什么的 node,kubectl get node --show-labels 显示可选键值
            values:
            -  n001
            operator: In		# 执行的操作
--------------------------------------------------------------------------------------------

operator 操作的种类

-In		 # label 的值在某个列表中
-NotIn  # label 的值在某个列表中
-Gt		 # label 的值大于某个值
-Lt		 # label 的值小于某个值
-Exsist	 # 某个 label 存在
-DoesNotExist # 某个 label 不存在

硬策略

​ —— 必须匹配的策略,不然 pod处于 pending

cat /kubeyml/affinity/req_pod001.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: req-pod
labels:
 app: req-pod
spec:
containers:
  - name: pre-con
    image: wangyanglinux/myapp:v2
    imagePullPolicy: IfNotPresent
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:  # 和软开头不一样
       nodeSelectorTerms:		# 和软一点都不一样
       - matchExpressions:
         - key: kubernetes.io/hostname
           values:
           - n002
           operator: In
--------------------------------------------------------------------------------------------

pod 亲和性

​ —— 即 pod 和 pod 直接根据 label 判断是否在同一个 拓扑域

cat /kubeyml/affinity/req_pod002.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: req-pod002
labels:
 app: req-pod002
spec:
containers:
  - name: req-con002
    image: wangyanglinux/myapp:v2
    imagePullPolicy: IfNotPresent
  affinity:
    podAffinity:  ###
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            values:
            - req-pod
            operator: In
        topologyKey: kubernetes.io/hostname
#    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              values:
              - pre-pod
              operator: In
          topologyKey: kubernetes.io/hostname   ## 选择拓扑域,即 hostname 相同的 node
--------------------------------------------------------------------------------------------

3. 污点和容忍

污点

  • Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去

容忍

  • 设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上

设置污点

`kubectl taint nodes` node-name key1=value1:effect
## effect 支持的三个选项:
	-NoSchedule  # 不会将 pod 调度到有该污点的 node 上
	-PreferNoSchedule  # 尽量避免将 pod 调度到有该污点的 node 上
	-NoExecute  # 不会将 pod 调度到有该污点的 node 上,同时会将 node 上已经存在的 pod 驱逐出去

查看污点

`kubectl describe node` node-name  | grep -i "taints:"

删除污点

`kubectl taint nodes` node-name key1=value1:effect- # 加个减号即可

设置容忍

cat /kubeyml/affinity/req_pod001.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Pod
metadata:
name: req-pod002
labels:
 app: req-pod002
spec:
containers:
  - name: pre-con002
    image: wangyanglinux/myapp:v2
    imagePullPolicy: IfNotPresent
  tolerations:				 # 设置容忍的 key value effect 必须一致!
  - key: auth    		  # 不指定 key 值时,表示容忍所有的污点 key      
    value: codefun 			
    operator: Equal			# 当值为 Exists 将会忽略 value 值
    effect: NoSchedule 		# 当不指定 effect 值时,表示容忍所有的污点作用
    tolerationSeconds: 3600 # 描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间
--------------------------------------------------------------------------------------------
当不指定 key 值时,表示容忍所有的污点

4. 固定节点调度

 cat /kubeyml/dep002.yml 
 --------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: dep-dep002
spec:
  replicas: 4
  template:
    metadata:
      labels:
        app: dep-dep002
    spec:
      nodeName: n001		# 设置指定节点的名字
#      nodeSelector:		# 根据标签选择,`kubectl label nodes` node-name key1=value1 给 pod 设置标签
#        app: myapp
      containers:
      - name: dep-con001
        image: wangyanglinux/myapp:v2
        ports:
        - containerPort: 80
--------------------------------------------------------------------------------------------

8. 集群安全

(一)机制说明

  • Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。
  • API Server 是集群内部各个组件通信的中介,也是外部控制的入口。
  • 所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。
  • Kubernetes 使用了认证(Authentication)、鉴权(Authorization)、准入控制(AdmissionControl)三步来保证API Server的安全

(二)认证介绍

  • 一共有三种认证方式(HTTP TOKENHTTP BaseHTTPS 证书),这里我们使用最为严格的HTTPS 证书认证
  • HTTPS 证书认证过程:首先客户端和服务端都需要向CA 架构申请证书,然后客户端和服务端通过证书来进行身份认证,最后通过随机私钥进行通讯
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zUmCPKI5-1653055923917)(D:\云计算工坊\图片\K8S\HTTPS 证书认证.png)]

(三)鉴权过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z89txgtv-1653055923918)(D:\云计算工坊\图片\K8S\鉴权.png)]

(四)实现鉴权

useradd devuser && passwd devuser # 创建实体用户 
mkdir /root/install-k8s/cert/devuser/ && vim  /root/install-k8s/cert/devuser/devuser-csr.json		# 编写 json 文件
--------------------------------------------------------------------------------------------
{
"CN": "devuser",   # 用户名
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048  
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
--------------------------------------------------------------------------------------------
# 下载 SSL 证书生成工具
	# 我们这里是直接将相关文件直接导入到 /usr/local/bin/ 下
chmod a+x *  # 给相关文件增加可执行权限
cd /etc/kubernetes/pki/
cfssl genecrt -ca=ca.crt -ca-key=ca.key -profile=kubernetes `/root/install-k8s/cert/devuser/devuser-csr.json` | cfssljson -bare `devuser`		# 有飘号的需要根据情况改动 
cd /root/install-k8s/cert/devuser/
export KUBE_APISERVER="https://192.168.80.142:6443"		# 设置集群参数
kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs=true --server=${KUBE_APISERVER} --kubeconfig=devuser.kubeconfig		# 设置集群参数
kubectl config set-credentials devuser --client-certificate=/etc/kubernetes/pki/devuser.pem --client-key=/etc/kubernetes/pki/devuser-key.pem --embed-certs=true --kubeconfig=devuser.kubeconfig		# 设置客户端认证参数
kubectl create namespace dev		# 创建用户管理的名称空间
kubectl config set-context kubernetes --cluster=kubernetes --user=devuser --namespace=dev --kubeconfig=devuser.kubeconfig		# 设置上下文
kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev		# 创建角色绑定,注意相关参数
mkdir /home/devuser/./kube
cp devuser.kubeconfig /home/devuser/.kube/
chown devuser:devuser /home/devuser/.kube/devuser.kubeconfig 
mv /home/devuser/.kube/devuser.kubeconfig /home/devuser/.kube/config
su - devuser
kubectl config use-context kubernetes --kubeconfig=config
kubectl run nginx --image=wangyanglinux/myapp:v2  # 创建 pod 并查看他属于能够名称空间

(五)准入控制

  • 准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过 Admission Controllers 实现,比如 ServiceAccount

9. Helm

1. 基础知识

1. 什么是 Helm

我们在部署各种 pod 的时候,需要编写各种组件的 yml 文件,非常繁琐。而 helm就像是 yum一样,将服务的所需的文件,组件打包到一起,从而实习服务的一键化部署。

helm 的重要组件

  • chart 是创建一个应用的信息集合,包括各种 Kubernetes 对象的配置模板、参数定义、依赖关系、文档说明等。chart 是应用部署的自包含逻辑单元。可以将 chart 想象成 aptyum 中的软件安装包

  • releasechart 的运行实例,代表了一个正在运行的应用,当 chart 被安装到 Kubernetes 集群,就生成一个 releasechart 能够多次安装到同一个集群,每次安装都是一个 release

2. Helm的架构

Helm 包含两个组件:Helm 客户端和 Tiller 服务器。Helm 客户端负责 chartrelease 的创建和管理以及和 Tiller 的交互。Tiller 服务器运行在 Kubernetes 集群中,它会处理 Helm 客户端的请求,与 Kubernetes API Server 交互

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1YNijK3-1653055923919)(D:\云计算工坊\图片\K8S\helm 架构.png)]

2. Helm部署

cd /usr/local/src
wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz
tar -zxvf helm-v2.13.1-linux-amd64.tar.gz 
cd linux-amd64/ && mv helm /usr/local/bin/
mkdir  /root/install-k8s/helm/
vim /root/install-k8s/helm/rbac-config.yml
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
--------------------------------------------------------------------------------------------
$ kubectl create -f rbac-config.yml
$ helm init --service-account tiller --skip-refresh
$ kubectl get pod -n kube-system # 查看相应的 pod 是否启动成功

3. 编写自定义chart

# 1. 编写 Chart.yaml 文件    必须以 yaml 结尾,不然不识别
mkdir /root/install-k8s/helm/hello-world/templates  && cd /root/install-k8s/helm/hello-world
vim Chart.yaml
--------------------------------------------------------------------------------------------
name: hello-world    # 必须存在的字段
version: 1.0.0		  # 必须存在的字段
--------------------------------------------------------------------------------------------
# 2. 编写服务相应的 yml 文件,且都在 templates 目录下
cd /root/install-k8s/helm/hello-world/templates
vim deployment.yml
--------------------------------------------------------------------------------------------
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: hello-world
spec:
replicas: 1
template:
 metadata:
   labels:
     app: hello-world
 spec:
   containers:
      - name: hello-world
        image: wangyanglinux/myapp:v1
        ports:
        - containerPort: 80
          protocol: TCP
--------------------------------------------------------------------------------------------
cat service.yml 
--------------------------------------------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: hello-world
--------------------------------------------------------------------------------------------
# 3. 回到有 Chart.yaml 文件的目录下进行安装操作
$ helm install .
# 4. 列出已经部署的 Release
$ helm list
NAME       REVISION	UPDATED     STATUS  	CHART      APP VERSION	NAMESPACE
voting-giraffe	1        	   DEPLOYED	   hello-world-1.0.0	      default  
# 5. 查询一个特定的 Release 的状态
$ helm status RELEASE_NAME
# 6. 移除所有与这个 Release 相关的 Kubernetes 资源
$ helm delete voting-giraffe
# 7. 显示没有完全删除的 release
$ helm list --deleted
NAME       REVISION	UPDATED     STATUS  	CHART      APP VERSION	NAMESPACE
voting-giraffe	1        	   DEPLOYED	   hello-world-1.0.0	      default  
# 8. helm rollback RELEASE_NAME REVISION_NUMBER  回滚操作
$ helm rollback voting-giraffe 1
# 9. 完全删除
$  helm delete --purge voting-giraffe
# 更新一个 release
$ helm upgrade voting-giraffe .
# 查看操作 release 历史
helm history voting-giraffe

设置参数

# 1. 在 Chart.yaml 文件的同级目录下创建参数文件
vim /root/install-k8s/helm/hello-world/values.yaml
--------------------------------------------------------------------------------------------
image:  
  repository: wangyanglinux/myapp  
  tag: 'v2'
--------------------------------------------------------------------------------------------
# 2. 修改原来的 deployment 文件的 image 字段
--------------------------------------------------------------------------------------------
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
--------------------------------------------------------------------------------------------
# 3. 更新 release
$ helm upgrade voting-giraffe .

4. 使用 helm 部署 dashboard

# 1. 获取安装文件源
$ helm repo add stable http://mirror.azure.cn/kubernetes/charts/
# 2. 拉取安装文件压缩包并解压
$ mkdir /root/install-k8s/helm/dashboard && cd !$
$ helm fetch stable/kubernetes-dashboard
$ tar -zxvf kubernetes-dashboard-1.11.1.tgz
# 3. 编写自定义配置文件
vim kubernetes-dashboard/kubernetes-dashboard.yaml 
--------------------------------------------------------------------------------------------
image:
repository: k8s.gcr.io/kubernetes-dashboard-amd64
tag: v1.10.1
ingress:
enabled: true
hosts:
    - k8s.frognew.com
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
  tls:
    - secretName: frognew-com-tls-secret
      hosts:
      - k8s.frognew.com
rbac:
  clusterAdminRole: true
--------------------------------------------------------------------------------------------
# 4. 安装 dashborad
$ helm install . -n kubernetes-dashboard --namespace kube-system  -f kubernetes-dashboard.yaml
# 5. 获取相关镜像
$ docker pull sacred02/kubernetes-dashboard-amd64:v1.10.1
$ docker tag sacred02/kubernetes-dashboard-amd64:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1
$ docker rmi -f sacred02/kubernetes-dashboard-amd64:v1.10.1
# 6. 将 ClusterIP 改为 NodePort
$ kubectl get svc -n kube-system
$ kubectl edit svc kubernetes-dashboard -n kube-system
# 7. 获取 token 令牌
$ kubectl -n kube-system get secret | grep kubernetes-dashboard-token   
$ kubectl describe secrect kubernetes-dashboard-token-cflxj -n kube-system
# 8. 在浏览器上访问

10. 普罗米修斯

1. 基础知识

1. 普罗米修斯的特点

  • 多维数据模型:由度量名称和键值对标识的时间序列数据(查找速度快)
  • 内置时间序列数据库:TSDB
  • promQL:灵活的查询语言,可以利用多维数据完成复杂查询
  • 基于HTTPpull的方式采集时间序列数据(export:比如 MySQL 没有相应的 HTTP 接口,就用 export来暴露,然后交由服务端)
  • 同时支持PushGateway组件收集数据(对于转瞬即逝的数据,export不能及时,由他来做)
  • 通过服务发现静态配置发现目标
  • 支持作为数据源接入Grafana

你可能感兴趣的:(虚拟化技术,kubernetes,docker,运维)