当前位置: 首页 > news >正文

网站建设有哪些特点广州信科做网站

网站建设有哪些特点,广州信科做网站,手机装修设计软件,设计感十足的网站Kubernetes-进阶 Pod详解 每个Pod中都可以包含一个或多个容器#xff0c;这些容器可以分两类 用户程序所在容器#xff0c;数量用户决定Pause容器#xff0c;这是每个Pod都会有的一个根容器#xff0c;它的作用有两个 可以以它为依据#xff0c;评估整个Pod的健康状态可以…Kubernetes-进阶 Pod详解 每个Pod中都可以包含一个或多个容器这些容器可以分两类 用户程序所在容器数量用户决定Pause容器这是每个Pod都会有的一个根容器它的作用有两个 可以以它为依据评估整个Pod的健康状态可以在根容器上设置Ip地址其它容器都可以通过此IP(Pod IP)就行Pod内部的网络通信Pod与Pod之间的网络通信是通过虚拟二层网络技术实现的我们当前的环境用的是Flannel Pod定义 以下是Pod的yaml中可配置的信息 使用命令kubectl explain pod可以看到pod的可配置的属性使用命令kubectl explain pod.metadata可以看到pod.metadata的可配置的属性 apiVersion: v1 #必选版本号例如v1 kind: Pod   #必选资源类型例如 Pod metadata:   #必选元数据name: string #必选Pod名称namespace: string #Pod所属的命名空间,默认为defaultlabels:    #自定义标签列表- name: string   spec: #必选Pod中容器的详细定义containers: #必选Pod中容器列表- name: string #必选容器名称image: string #必选容器的镜像名称imagePullPolicy: [ Always|Never|IfNotPresent ] #获取镜像的策略 command: [string] #容器的启动命令列表如不指定使用打包时使用的启动命令args: [string] #容器的启动命令参数列表workingDir: string #容器的工作目录volumeMounts: #挂载到容器内部的存储卷配置- name: string #引用pod定义的共享存储卷的名称需用volumes[]部分定义的的卷名mountPath: string #存储卷在容器内mount的绝对路径应少于512字符readOnly: boolean #是否为只读模式ports: #需要暴露的端口库号列表- name: string #端口的名称containerPort: int #容器需要监听的端口号hostPort: int #容器所在主机需要监听的端口号默认与Container相同protocol: string #端口协议支持TCP和UDP默认TCPenv: #容器运行前需设置的环境变量列表- name: string #环境变量名称value: string #环境变量的值resources: #资源限制和请求的设置limits: #资源限制的设置cpu: string #Cpu的限制单位为core数将用于docker run --cpu-shares参数memory: string #内存限制单位可以为Mib/Gib将用于docker run --memory参数requests: #资源请求的设置cpu: string #Cpu请求容器启动的初始可用数量memory: string #内存请求,容器启动的初始可用数量lifecycle: #生命周期钩子postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止livenessProbe: #对Pod内各容器健康检查的设置当探测无响应几次后将自动重启该容器exec:   #对Pod容器内检查方式设置为exec方式command: [string] #exec方式需要制定的命令或脚本httpGet: #对Pod内个容器健康检查方法设置为HttpGet需要制定Path、portpath: stringport: numberhost: stringscheme: stringHttpHeaders:- name: stringvalue: stringtcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式port: numberinitialDelaySeconds: 0 #容器启动完成后首次探测的时间单位为秒timeoutSeconds: 0    #对容器健康检查探测等待响应的超时时间单位秒默认1秒periodSeconds: 0    #对容器监控检查的定期探测时间设置单位秒默认10秒一次successThreshold: 0failureThreshold: 0securityContext:privileged: falserestartPolicy: [Always | Never | OnFailure] #Pod的重启策略nodeName: string #设置NodeName表示将该Pod调度到指定到名称的node节点上nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上imagePullSecrets: #Pull镜像时使用的secret名称以keysecretkey格式指定- name: stringhostNetwork: false #是否使用主机网络模式默认为false如果设置为true表示使用宿主机网络volumes: #在该pod上定义共享存储卷列表- name: string #共享存储卷名称 volumes类型有很多种emptyDir: {} #类型为emtyDir的存储卷与Pod同生命周期的一个临时目录。为空值hostPath: string #类型为hostPath的存储卷表示挂载Pod所在宿主机的目录path: string    #Pod所在宿主机的目录将被用于同期中mount的目录secret:    #类型为secret的存储卷挂载集群与定义的secret对象到容器内部scretname: string items: - key: stringpath: stringconfigMap: #类型为configMap的存储卷挂载预定义的configMap对象到容器内部name: stringitems:- key: stringpath: stringPod配置 Pod一级属性 属性名说明apiVersion 版本由kubernetes内部定义版本号必须可以用 kubectl api-versions 查询到kind 类型由kubernetes内部定义版本号必须可以用 kubectl api-resources 查询到metadata 元数据主要是资源标识和说明常用的有name、namespace、labels等spec 描述这是配置中最重要的一部分里面是对各种资源配置的详细描述status 状态信息里面的内容不需要定义由kubernetes自动生成 spec属性(重点) 属性名说明containers []Object容器列表用于定义容器的详细信息nodeName 根据nodeName的值将pod调度到指定的Node节点上nodeSelector map[]根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上hostNetwork 是否使用主机网络模式默认为false如果设置为true表示使用宿主机网络volumes []Object存储卷用于定义Pod上面挂在的存储信息restartPolicy 重启策略表示Pod在遇到故障的时候的处理策略 基本配置 使用kubectl explain pod.spec.containers命令查看可配置项 KIND: Pod VERSION: v1 RESOURCE: containers []Object # 数组代表可以有多个容器 FIELDS:name string # 容器名称image string # 容器需要的镜像地址imagePullPolicy string # 镜像拉取策略 command []string # 容器的启动命令列表如不指定使用打包时使用的启动命令args []string # 容器的启动命令需要的参数列表env []Object # 容器环境变量的配置ports []Object # 容器需要暴露的端口号列表resources Object # 资源限制和资源请求的设置案例 创建pod-base.yaml文件内容如下 使用kubectl apply/delete启动(更新)/删除 Pod apiVersion: v1 kind: Pod metadata:name: pod-basenamespace: devlabels:user: test spec:containers:- name: nginximage: nginx:1.17.1- name: busyboximage: busybox:1.30上面定义了一个比较简单Pod的配置里面有两个容器 nginx用1.17.1版本的nginx镜像创建nginx是一个轻量级web容器busybox用1.30版本的busybox镜像创建busybox是一个小巧的linux命令集合 镜像拉取 创建pod-imagepullpolicy.yaml文件内容如下 apiVersion: v1 kind: Pod metadata:name: pod-imagepullpolicynamespace: dev spec:containers:- name: nginximage: nginx:1.17.1imagePullPolicy: Never # 用于设置镜像拉取策略- name: busyboximage: busybox:1.30imagePullPolicy用于设置镜像拉取策略kubernetes支持配置三种拉取策略 Always总是从远程仓库拉取镜像一直远程下载IfNotPresent本地有则使用本地镜像本地没有则从远程仓库拉取镜像本地有就本地 本地没远程下载Never只使用本地镜像从不去远程仓库拉取本地没有就报错 一直使用本地 默认值说明 如果镜像tag为具体版本号 默认策略是IfNotPresent 如果镜像tag为latest最终版本 默认策略是always 启动命令 在说启动命令之前先说说在前面执行的2个脚本可以冲READY中看到只启动了一个通过使用kubectl describe pod pod命令 -n dev命令可以看到busybox启动不了这是因为busybox启动后没有进程在运行所以启动后就自动关闭了那么如何让容器启动后不会自动关闭呢写过Dockerfile的小伙伴应该就清楚只需要在后面执行一个CMD命令即可 创建pod-command.yaml文件内容如下 apiVersion: v1 kind: Pod metadata:name: pod-commandnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1- name: busyboximage: busybox:1.30command: [/bin/sh,-c,touch /tmp/hello.txt;while true;do /bin/echo $(date %T) /tmp/hello.txt; sleep 3; done;] #执行cmd命令使得容器有进程在运行command用于在pod中的容器初始化完毕之后运行一个命令。 “/bin/sh”,“-c”, 使用sh执行命令 touch /tmp/hello.txt; 创建一个/tmp/hello.txt 文件 while true;do /bin/echo $(date %T) /tmp/hello.txt; sleep 3; done; 每隔3秒向文件中写入当前时间 进入容器测试 # 进入pod中的busybox容器查看文件内容 # 补充一个命令: kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 在容器内部执行命令 # 使用这个命令就可以进入某个容器的内部然后进行相关操作了 # 比如可以查看txt文件的内容 [rootk8s-master01 pod]# kubectl exec pod-command -n dev -it -c busybox /bin/sh #进入容器后执行 tail -f /tmp/hello.txt特别说明 通过上面发现command已经可以完成启动命令和传递参数的功能为什么这里还要提供一个args选项用于传递参数呢?这其实跟docker有点关系kubernetes中的command、args两项其实是实现覆盖Dockerfile中ENTRYPOINT的功能。 1 如果command和args均没有写那么用Dockerfile的配置。 2 如果command写了但args没有写那么Dockerfile默认的配置会被忽略执行输入的command 3 如果command没写但args写了那么Dockerfile中配置的ENTRYPOINT的命令会被执行使用当前args的参数 4 如果command和args都写了那么Dockerfile的配置被忽略执行command并追加上args参数 环境变量 创建pod-env.yaml文件内容如下 apiVersion: v1 kind: Pod metadata:name: pod-envnamespace: dev spec:containers:- name: busyboximage: busybox:1.30command: [/bin/sh,-c,while true;do /bin/echo $(date %T);sleep 60; done;]env: # 设置环境变量列表- name: usernamevalue: admin- name: passwordvalue: 123456进入容器测试 # 进入容器输出环境变量 kubectl exec pod-env -n dev -c busybox -it /bin/sh #进入容器后执行 echo $username echo $password端口设置 使用kubectl explain pod.spec.containers.ports命令查看可配置项 KIND: Pod VERSION: v1 RESOURCE: ports []Object FIELDS:name string # 端口名称如果指定必须保证name在pod中是唯一的 containerPortinteger # 容器要监听的端口(0x65536)hostPort integer # 容器要在主机上公开的端口如果设置主机上只能运行容器的一个副本(一般省略) hostIP string # 要将外部端口绑定到的主机IP(一般省略)protocol string # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”。创建pod-ports.yaml内容如下 apiVersion: v1 kind: Pod metadata:name: pod-portsnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports: # 设置容器暴露的端口列表- name: nginx-portcontainerPort: 80protocol: TCP访问容器中的程序需要使用的是Podip:containerPort 资源配额 容器中的程序要运行肯定是要占用一定资源的比如cpu和内存等如果不对某个容器的资源做限制那么它就可能吃掉大量资源导致其它容器无法运行。针对这种情况kubernetes提供了对内存和cpu的资源进行配额的机制这种机制主要通过resources选项实现他有两个子选项 limits用于限制运行时容器的最大占用资源当容器占用资源超过limits时会被终止并进行重启requests 用于设置容器需要的最小资源如果环境资源不够容器将无法启动 创建pod-resources.yaml内容如下 apiVersion: v1 kind: Pod metadata:name: pod-resourcesnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1resources: # 资源配额limits: # 限制资源上限cpu: 2 # CPU限制单位是core数memory: 10Gi # 内存限制requests: # 请求资源下限cpu: 1 # CPU限制单位是core数memory: 10Mi # 内存限制Pod生命周期 现在以一般的Pod对象从创建至终的这段实际范围称为pod的生命周期它主要包含以下的过程 pod创建过程运行初始化容器(init container)过程运行主容器(main container)过程 容器启动后钩子(post start)、容器终止前钩子(pre stop)容器的存活性探测(livenes porbe)、就绪性探测(readiness probe pod终止 在整个的生命周期中pod会出现5种状态 状态说明挂起PendingapiService已经创建了pod资源对象但它尚未被调度卧槽或者仍处于下载镜像的过程中运行种Runningpod已经被调度至某个节点并且所有容器都已经被kubelet创建完成成功Succeededpod中的所有容器都已经成功终止并且不会被重启失败Failed所有容器都已经终止但至少有一个容器终止失败即容器返回了非0值的退出状态未知Unknownapiserver无法正常获取到pod对象的状态信息通常由网络通讯失败所导致 创建过程 用户通过kubectl或者其它api客户端提交需要创建的pod信息给apiServerapiServer开始生成pod对象的信息并将信息存入etcd然后返回确认信息到客户端apiServer开始反映etcd中的pod对象变化其它组件使用watch机制来跟踪检查apiServer上变动cheduler发现有新的pod对象要创建开始为Pod分配主机并将结果更新至apiServernode节点上的kubelet发现有pod调度过来尝试调用docker启动容器并将结果送会至apiServerapiServer将接收到的pod状态信息存入etcd中 终止过程 用户向apiServer发生删除pod对象命令apiServer中的pod对象信息会随时间的推移而更新宽限期内(默认30s)pod被视为dead将pod标记为terminating状态kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程端点控制器监控到pod对象的关闭行为是将其所有匹配到此端点的service资源从端点列表中移除如果当前pod对象定义了preStop钩子处理器则在标记为terminating后即同步的方式启动执行pod对象中的容器进程收到停止信号宽限期结束后若pod中还存在运行的进程那么pod对象会收到立刻终止的信号kubelet请求apiserver将此pod资源宽限期设置为0从而完成删除操作此时pod对应用户已经不可见 初始化容器 初始化容器是在pod的主容器启动之前要运行的容器主要是做一些主容器的前置工作它有2大特性 初始化容器必须完成直到结束若某个初始化容器运行失败那么kubernetes需要重启它直到成功完成初始化容器必须按照定义的顺序执行当且仅当前一个成功后后面的一个才能运行 初始化容器有很多应用场景下面是常见的场景 提供主容器镜像中不具备的工具程序或自定义代码初始化容器要先于运用容器串行启动并运行完成因此可以作为后续容器启动的依赖条件是否满足判断 案例 以下设计一个案例假设主容器运行nginx但是在nginx运行之前需要确保能够先连接上mysql和redis服务器 假如mysql 192.168.10.101 redis 192.168.10.102 创建pod-initcontainer.yaml内容如下 apiVersion: v1 kind: Pod metadata:name: pod-initcontainernamespace: dev spec:containers:- name: main-containerimage: nginx:1.17.1ports: - name: nginx-portcontainerPort: 80initContainers:- name: test-mysqlimage: busybox:1.30command: [sh, -c, until ping 192.168.10.101 -c 1 ; do echo waiting for mysql...; sleep 2; done;]- name: test-redisimage: busybox:1.30command: [sh, -c, until ping 192.168.10.102 -c 1 ; do echo waiting for reids...; sleep 2; done;]执行命令测试 # 创建pod kubectl create -f pod-initcontainer.yaml # 查看pod状态 # 发现pod卡在启动第一个初始化容器过程中后面的容器不会运行 kubectl describe pod pod-initcontainer -n dev # 动态查看pod kubectl get pods pod-initcontainer -n dev -w # 接下来新开一个shell为当前服务器新增两个ip观察pod的变化 [rootk8s-master01 ~]# ifconfig ens33:1 192.168.5.14 netmask 255.255.255.0 up [rootk8s-master01 ~]# ifconfig ens33:2 192.168.5.15 netmask 255.255.255.0 up钩子函数 Kubernetes在主容器的启动之后和停止之前提供两个钩子函数 post start容器创建之后执行如果失败了会重启容器pre stop容器终止之前执行执行完成之后容器将成功终止在其完成之前会阻塞删除容器的操作 钩子处理支持以下3种方式 Exec命令 在容器内执行一次命令 lifecycle:postStart: exec:command:- cat- /tmp/healthyTCPSocket 在当前容器尝试访问指定的socket lifecycle:postStart:tcpSocket:port: 8080HTTPGet 在当前容器中向某url发起http请求 lifecycle:postStart:httpGet:path: / #URI地址port: 80 #端口号host: 192.168.5.3 #主机地址scheme: HTTP #支持的协议http或者https案例 创建pod-hook-exec.yaml文件内容如下 apiVersion: v1 kind: Pod metadata:name: pod-hook-execnamespace: dev spec:containers:- name: main-containerimage: nginx:1.17.1ports:- name: nginx-portcontainerPort: 80lifecycle:postStart: exec: # 在容器启动的时候执行一个命令修改掉nginx的默认首页内容command: [/bin/sh, -c, echo postStart... /usr/share/nginx/html/index.html]preStop:exec: # 在容器停止之前停止nginx服务command: [/usr/sbin/nginx,-s,quit]执行命令测试 # 创建pod kubectl create -f pod-hook-exec.yaml # 查看pod kubectl get pods pod-hook-exec -n dev -o wide # 访问 curl 10.244.22.48容器探测 容器探测用于检测容器中的应用是否正常工作是保障业务可用性的一种传统机制如果结果探测实例的状态不符合预期那么kubernetes就会把该问题实例摘除不承担业务流量kubernetes提供了两种探针来实现容器探测 liveness probes存活性探针用于检测应用实例当前是否处于正常运行状态如果不是k8s会重启容器 readiness probes就绪性探针用于检测应用实例当前是否可以接收请求如果不能k8s不会转发流量 livenessProbe 决定是否重启容器readinessProbe 决定是否将请求转发给容器 探针3种方式 #查看探测可配置参数 kubectl explain pod.spec.containers.livenessProbe FIELDS:exec Object tcpSocket ObjecthttpGet ObjectinitialDelaySeconds integer # 容器启动后等待多少秒执行第一次探测timeoutSeconds integer # 探测超时时间。默认1秒最小1秒periodSeconds integer # 执行探测的频率。默认是10秒最小1秒failureThreshold integer # 连续探测失败多少次才被认定为失败。默认是3。最小值是1successThreshold integer # 连续探测成功多少次才被认定为成功。默认是1Exec命令 在容器内执行一次命令如果命令执行的退出码为0则认为程序正常否则不正常 livenessProbe:postStart: exec:command:- cat- /tmp/healthyTCPSocket 将会尝试访问一个用户容器的端口如果能够建立这条连接则认为程序正常否则不正常 livenessProbe:postStart:tcpSocket:port: 8080HTTPGet 调用容器内Web应用的URL如果返回的状态码在200和399之间则认为程序正常否则不正常 livenessProbe:postStart:httpGet:path: / #URI地址port: 80 #端口号host: 192.168.5.3 #主机地址scheme: HTTP #支持的协议http或者https案例 创建pod-liveness-exec.yaml apiVersion: v1 kind: Pod metadata:name: pod-liveness-execnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports: - name: nginx-portcontainerPort: 80livenessProbe:exec:command: [/bin/cat,/tmp/hello.txt] # 执行一个查看文件的命令执行命令测试 # 创建Pod kubectl create -f pod-liveness-exec.yaml # 查看Pod详情 kubectl describe pods pod-liveness-exec -n dev # 观察上面的信息就会发现nginx容器启动之后就进行了健康检查 # 检查失败之后容器被kill掉然后尝试进行重启这是重启策略的作用后面讲解 # 稍等一会之后再观察pod信息就可以看到RESTARTS不再是0而是一直增长 kubectl get pods pod-liveness-exec -n dev由于nginx不存在hello.txt文件可以看到pod在不断重启 重启策略 在上一节中一旦容器探测出现了问题kubernetes就会对容器所在的Pod进行重启其实这是由pod的重启策略决定的pod的重启策略有 3 种分别如下 Always 容器失效时自动重启该容器这也是默认值。OnFailure 容器终止运行且退出码不为0时重启Never 不论状态为何都不重启该容器 重启策略适用于pod对象中的所有容器首次需要重启的容器将在其需要时立即进行重启随后再次需要重启的操作将由kubelet延迟一段时间后进行且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s300s是最大延迟时长。 创建pod-restartpolicy.yaml apiVersion: v1 kind: Pod metadata:name: pod-restartpolicynamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports:- name: nginx-portcontainerPort: 80livenessProbe:httpGet:scheme: HTTPport: 80path: /hellorestartPolicy: Never # 设置重启策略为Never# 创建Pod kubectl create -f pod-restartpolicy.yaml # 查看Pod详情发现nginx容器失败 kubectl describe pods pod-restartpolicy -n dev # 多等一会再观察pod的重启次数发现一直是0并未重启 kubectl get pods pod-restartpolicy -n devPod调度 默认情况下一个Pod在那个Node节点上运行是由Scheduler组件采用相应的算法计算出来的这个过程是不受人工控制的但是在实际使用中这并不满足需求因为很多情况下我们想控制Pod部署在某个节点上这就需要了解和使用Pod的调度规则Kubernetes提供四大类的调度方式 自动调度(默认)运行在哪个节点上完全由Scheduler经过计算得出定向调度NodeName、NodeSelector亲和性调度NodeAffinity、PodAffinity、PodAntiAffinity污点(容忍)调度Taints、Toleration 定向调度 定向调度是一种强制性调度指的是利用在pod上声明nodeName或者nodeSelector以此将Pod调度到期望的node节点上 注意由于调度是强制性的意味着即使要调度的目标Node不存在也会进行调度只不过pod运行失败 nodeName NodeName用于强制约束将Pod调度到指定的Name的Node节点上这种方式直接通过Scheduler的调度逻辑直接将Pod调度到指定名称的节点 创建一个pod-nodename.yaml文件 apiVersion: v1 kind: Pod metadata:name: pod-nodenamenamespace: dev spec:containers:- name: nginximage: nginx:1.17.1nodeName: node1 # 指定调度到node1节点上执行命令测试 #创建Pod kubectl create -f pod-nodename.yaml #查看Pod调度到NODE属性确实是调度到了node1节点上 kubectl get pods pod-nodename -n dev -o wide nodeSelector NodeSelector用于强制约束将Pod调度到指定标签的node节点上它是通过kubernetes的label-selector机制实现的在pod创建之前会由scheduler使用MatchNodeSelector调度策略进行label匹配找到目标node然后将pod调度到目标node节点上 首先需要为node节点添加标签 kubectl label nodes node1 nodeenvpro kubectl label nodes node2 nodeenvtest创建一个pod-nodeselector.yaml文件 apiVersion: v1 kind: Pod metadata:name: pod-nodeselectornamespace: dev spec:containers:- name: nginximage: nginx:1.17.1nodeSelector: nodeenv: pro # 指定调度到具有nodeenvpro标签的节点上执行命令测试 #创建Pod kubectl create -f pod-nodeselector.yaml #查看Pod调度到NODE属性确实是调度到了node1节点上 kubectl get pods pod-nodeselector -n dev -o wide亲和性调度 在上面的定向调度使用过程中发现存在一个缺点假如调度的目标不存在那么pod就不会启动成功基于这个问题Kubernetes还提供了亲和性调度(Affinity)它在NodeSelector的基础之上进行了扩展可以通过配置的形式实现优先满足条件的Node进行调度如果没有也可以调度到不满足条件的Node上使得调度更加灵活 Affinit主要分3类 nodeAffinity(node亲和性)以node为目标解决pod可以调度到那些node的问题podAffinity(pod亲和性)以pod为目标解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题podAntiAffinity(pod反亲和性)以pod为目标解决pod不能和那些已存在pod部署在同一个拓扑域种的问题 关于亲和性(反亲和性)使用场景的说明 亲和性如果两个应用频繁交互那就有必要利用亲和性让两个应用的尽可能的靠近这样可以减少因网络通信而带来的性能损耗 反亲和性当应用的采用多副本部署时有必要采用反亲和性让各个应用实例打散分布在各个node上这样可以提高服务的高可用性 nodeAffinity 首先来看一下NodeAffinity的可配置项 pod.spec.affinity.nodeAffinityrequiredDuringSchedulingIgnoredDuringExecution Node节点必须满足指定的所有规则才可以相当于硬限制nodeSelectorTerms 节点选择列表matchFields 按节点字段列出的节点选择器要求列表matchExpressions 按节点标签列出的节点选择器要求列表(推荐)key 键values 值operator 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, LtpreferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node相当于软限制 (倾向)preference 一个节点选择器项与相应的权重相关联matchFields 按节点字段列出的节点选择器要求列表matchExpressions 按节点标签列出的节点选择器要求列表(推荐)key 键values 值operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Ltweight 倾向权重在范围1-100。关系符的使用说明:- matchExpressions:- key: nodeenv # 匹配存在标签的key为nodeenv的节点operator: Exists- key: nodeenv # 匹配标签的key为nodeenv,且value是xxx或yyy的节点operator: Invalues: [xxx,yyy]- key: nodeenv # 匹配标签的key为nodeenv,且value大于xxx的节点operator: Gtvalues: xxx硬限制 使用requiredDuringSchedulingIgnoredDuringExecution 创建pod-nodeaffinity-required.yaml apiVersion: v1 kind: Pod metadata:name: pod-nodeaffinity-requirednamespace: dev spec:containers:- name: nginximage: nginx:1.17.1affinity: #亲和性设置nodeAffinity: #设置node亲和性requiredDuringSchedulingIgnoredDuringExecution: # 硬限制nodeSelectorTerms:- matchExpressions: # 匹配env的值在[xxx,yyy]中的标签- key: nodeenvoperator: Invalues: [xxx,yyy]执行命令测试 # 创建pod kubectl create -f pod-nodeaffinity-required.yaml # 查看pod状态 运行失败 kubectl get pods pod-nodeaffinity-required -n dev -o wide # 查看Pod的详情 # 发现调度失败提示node选择失败 kubectl describe pod pod-nodeaffinity-required -n dev#接下来停止pod kubectl delete -f pod-nodeaffinity-required.yaml # 修改文件将values: [xxx,yyy]------ [pro,yyy] vim pod-nodeaffinity-required.yaml # 再次启动 kubectl create -f pod-nodeaffinity-required.yaml # 此时查看发现调度成功已经将pod调度到了node1上 kubectl get pods pod-nodeaffinity-required -n dev -o wide软限制 使用preferredDuringSchedulingIgnoredDuringExecution 创建pod-nodeaffinity-preferred.yaml apiVersion: v1 kind: Pod metadata:name: pod-nodeaffinity-preferrednamespace: dev spec:containers:- name: nginximage: nginx:1.17.1affinity: #亲和性设置nodeAffinity: #设置node亲和性preferredDuringSchedulingIgnoredDuringExecution: # 软限制- weight: 1preference:matchExpressions: # 匹配env的值在[xxx,yyy]中的标签(当前环境没有)- key: nodeenvoperator: Invalues: [xxx,yyy]执行命令测试 # 创建pod kubectl create -f pod-nodeaffinity-preferred.yaml # 查看pod状态 运行成功 kubectl get pod pod-nodeaffinity-preferred -n devNodeAffinity规则设置的注意事项 1 如果同时定义了nodeSelector和nodeAffinity那么必须两个条件都得到满足Pod才能运行在指定的Node上 2 如果nodeAffinity指定了多个nodeSelectorTerms那么只需要其中一个能够匹配成功即可 3 如果一个nodeSelectorTerms中有多个matchExpressions 则一个节点必须满足所有的才能匹配成功 4 如果一个pod所在的Node在Pod运行期间其标签发生了改变不再符合该Pod的节点亲和性需求则系统将忽略此变化 podAffinity 首先来看一下podAffinity的可配置项 pod.spec.affinity.podAffinityrequiredDuringSchedulingIgnoredDuringExecution 硬限制namespaces 指定参照pod的namespacetopologyKey 指定调度作用域labelSelector 标签选择器matchExpressions 按节点标签列出的节点选择器要求列表(推荐)key 键values 值operator 关系符 支持In, NotIn, Exists, DoesNotExist.matchLabels 指多个matchExpressions映射的内容preferredDuringSchedulingIgnoredDuringExecution 软限制podAffinityTerm 选项namespaces topologyKeylabelSelectormatchExpressions key 键values 值operatormatchLabels weight 倾向权重在范围1-100topologyKey用于指定调度时作用域,例如: 如果指定为kubernetes.io/hostname那就是以Node节点为区分范围 如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分 硬限制 首先创建一个参照Podpod-podaffinity-target.yaml apiVersion: v1 kind: Pod metadata:name: pod-podaffinity-targetnamespace: devlabels:podenv: pro #设置标签 spec:containers:- name: nginximage: nginx:1.17.1nodeName: node1 # 将目标pod名确指定到node1上执行命令启动 # 启动目标pod kubectl create -f pod-podaffinity-target.yaml # 查看pod状况 kubectl get pods pod-podaffinity-target -n dev创建pod-podaffinity-required.yaml 新Pod必须要与拥有标签nodeenvxxx或者nodeenvyyy的pod在同一Node上显然现在没有这样pod apiVersion: v1 kind: Pod metadata:name: pod-podaffinity-requirednamespace: dev spec:containers:- name: nginximage: nginx:1.17.1affinity: #亲和性设置podAffinity: #设置pod亲和性requiredDuringSchedulingIgnoredDuringExecution: # 硬限制- labelSelector:matchExpressions: # 匹配env的值在[xxx,yyy]中的标签- key: podenvoperator: Invalues: [xxx,yyy]topologyKey: kubernetes.io/hostname执行命令测试 # 启动pod kubectl create -f pod-podaffinity-required.yaml # 查看pod状态发现未运行 kubectl get pods pod-podaffinity-required -n dev # 查看详细信息 kubectl describe pods pod-podaffinity-required -n dev# 接下来修改 values: [xxx,yyy]-----values:[pro,yyy] # 意思是新Pod必须要与拥有标签nodeenvxxx或者nodeenvyyy的pod在同一Node上 vim pod-podaffinity-required.yaml # 然后重新创建pod查看效果 kubectl delete -f pod-podaffinity-required.yaml kubectl create -f pod-podaffinity-required.yaml # 发现此时Pod运行正常 kubectl get pods pod-podaffinity-required -n dev软限制 软限制就不进行演示了在使用方法大同小异 podAntiAffinity PodAntiAffinity主要实现以运行的Pod为参照让新创建的Pod跟参照pod不在一个区域中的功能它的配置方式和选项跟PodAffinty是一样的这里不再做详细解释直接做一个测试案例 硬限制 继续使用上个案例中目标pod #可以看到pod-podaffinity-target有一个podenv标签并且当前在node1节点上 kubectl get pods -n dev -o wide --show-labels创建pod-podantiaffinity-required.yaml apiVersion: v1 kind: Pod metadata:name: pod-podantiaffinity-requirednamespace: dev spec:containers:- name: nginximage: nginx:1.17.1affinity: #亲和性设置podAntiAffinity: #设置pod亲和性requiredDuringSchedulingIgnoredDuringExecution: # 硬限制- labelSelector:matchExpressions: # 匹配podenv的值在[pro]中的标签- key: podenvoperator: Invalues: [pro]topologyKey: kubernetes.io/hostname执行命令测试 # 创建pod kubectl create -f pod-podantiaffinity-required.yaml # 查看pod # 发现调度到了node2上 kubectl get pods pod-podantiaffinity-required -n dev -o wide软限制 软限制就不进行演示了在使用方法大同小异 污点(容忍)调度 污点(Taints) 前面的调度方式都是站在Pod的角度上通过在Pod上添加属性来确定Pod是否要调度到指定的Node上其实我们也可以站在Node的角度上通过在Node上添加污点属性来决定是否允许Pod调度过来。 Node被设置上污点之后就和Pod之间存在了一种相斥的关系进而拒绝Pod调度进来甚至可以将已经存在的Pod驱逐出去。 污点的格式为keyvalue:effect, key和value是污点的标签effect描述污点的作用支持如下三个选项 PreferNoSchedulekubernetes将尽量避免把Pod调度到具有该污点的Node上除非没有其他节点可调度NoSchedulekubernetes将不会把Pod调度到具有该污点的Node上但不会影响当前Node上已存在的PodNoExecutekubernetes将不会把Pod调度到具有该污点的Node上同时也会将Node上已存在的Pod驱离 使用kubectl设置和去除污点的命令示例如下 # 设置污点 kubectl taint nodes node1 keyvalue:effect # 去除污点 kubectl taint nodes node1 key:effect- # 去除所有污点 kubectl taint nodes node1 key-使用kubeadm搭建的集群默认就会给master节点添加一个污点标记,所以pod就不会调度到master节点上. 容忍(Toleration) 上面介绍了污点的作用我们可以在node上添加污点用于拒绝pod调度上来但是如果就是想将一个pod调度到一个有污点的node上去这时候应该怎么做呢这就要使用到容忍 下面看一下容忍的详细配置 FIELDS:key # 对应着要容忍的污点的键空意味着匹配所有的键value # 对应着要容忍的污点的值operator # key-value的运算符支持Equal和Exists默认effect # 对应污点的effect空意味着匹配所有影响tolerationSeconds # 容忍时间, 当effect为NoExecute时生效表示pod在Node上的停留时间容忍某个污点yaml配置如下 apiVersion: v1 kind: Pod metadata:name: pod-tolerationnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1tolerations: # 添加容忍- key: tag # 要容忍的污点的keyoperator: Equal # 操作符value: test # 容忍的污点的valueeffect: NoExecute # 添加容忍的规则这里必须和标记的污点规则相同Pod控制器 Pod控制器是管理pod的中间层使用了pod控制器之后我们只需要告诉pod控制器想要多少个什么样的pod就可以了塌就会创建出满足条件的pod并确保每一个pod处于用户期望的状态如果pod在运行中出现故障控制器会基于策略重建pod 在Kubernetes中pod控制器的创建方式可发两类 自主式podKubernetes直接创建出来的pod这种pod删除后就没有了也不会重建控制器创建pod通过控制器创建的pod这种pod删除了之后还会自动重建 在Kubernets种有很多类型的pod控制器每种都有自己合适的场景常见有如下 控制器说明ReplicationController比较原始的pod控制器已经被废弃由ReplicaSet替代ReplicaSet保证副本数量一直维持在期望值并支持pod数量扩缩容镜像版本升级Deployment通过控制ReplicaSet来控制Pod并支持滚动升级、回退版本Horizontal Pod Autoscaler可以根据集群负载自动水平调整Pod的数量实现削峰填谷DaemonSet在集群中的指定Node上运行且仅运行一个副本一般用于守护进程类的任务Job它创建出来的pod只要完成任务就立即退出不需要重启或重建用于执行一次性任务Cronjob它创建的Pod负责周期性任务控制不需要持续后台运行StatefulSet管理有状态应用 ReplicaSet(RS) ReplicaSet的主要作用是保证一定数量的pod正常运行它会持续监听这些Pod的运行状态一旦Pod发生故障就会重启或重建。同时它还支持对pod数量的扩缩容和镜像版本的升降级 yaml配置模板 apiVersion: apps/v1 # 版本号 kind: ReplicaSet # 类型 metadata: # 元数据name: # rs名称 namespace: # 所属命名空间 labels: #标签controller: rs spec: # 详情描述replicas: 3 # 副本数量selector: # 选择器通过它指定该控制器管理哪些podmatchLabels: # Labels匹配规则app: nginx-podmatchExpressions: # Expressions匹配规则- {key: app, operator: In, values: [nginx-pod]}template: # 模板当副本数量不足时会根据下面的模板创建pod副本metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80在这里面需要新了解的配置项就是spec下面几个选项 replicas指定副本数量其实就是当前rs创建出来的pod的数量默认为1 selector选择器它的作用是建立pod控制器和pod之间的关联关系采用的Label Selector机制 在pod模板上定义label在控制器上定义选择器就可以表明当前控制器能管理哪些pod了 template模板就是当前控制器创建pod所使用的模板板里面其实就是前一章学过的pod的定义 创建 创建pc-replicaset.yaml文件 apiVersion: apps/v1 kind: ReplicaSet metadata:name: pc-replicasetnamespace: dev spec:replicas: 3selector: matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1# 创建rs kubectl create -f pc-replicaset.yaml # 查看rs # DESIRED:期望副本数量 # CURRENT:当前副本数量 # READY:已经准备好提供服务的副本数量 kubectl get rs pc-replicaset -n dev -o wide # 查看当前控制器创建出来的pod # 这里发现控制器创建出来的pod的名称是在控制器名称后面拼接了-xxxxx随机码 kubectl get pod -n dev扩缩容 # 方式一 # 编辑rs的副本数量修改spec:replicas: 6即可 kubectl edit rs pc-replicaset -n dev # 查看pod kubectl get pods -n dev# 方式二 # 当然也可以直接使用命令实现 # 使用scale命令实现扩缩容 后面--replicasn直接指定目标数量即可 kubectl scale rs pc-replicaset --replicas2 -n dev # 命令运行完毕立即查看发现已经有4个开始准备退出了 # 稍等片刻就只剩下2个了 kubectl get pods -n dev镜像升级 # 方式一 # 编辑rs的容器镜像 - image: nginx:1.17.2 kubectl edit rs pc-replicaset -n dev # 再次查看发现镜像版本已经变更了 kubectl get rs -n dev -o wide# 方式二 # 同样的道理也可以使用命令完成这个工作 # kubectl set image rs rs名称 容器镜像版本 -n namespace kubectl set image rs pc-replicaset nginxnginx:1.17.1 -n dev # 再次查看发现镜像版本已经变更了 kubectl get rs -n dev -o wide删除 #方式一 # 使用kubectl delete命令会删除此RS以及它管理的Pod # 在kubernetes删除RS前会将RS的replicasclear调整为0等待所有的Pod被删除后在执行RS对象的删除 kubectl delete rs pc-replicaset -n dev kubectl get pod -n dev -o wide#方式二 # 如果希望仅仅删除RS对象保留Pod可以使用kubectl delete命令时添加--cascadefalse选项不推荐。 kubectl delete rs pc-replicaset -n dev --cascadefalse kubectl get pods -n dev#方式三 # 也可以使用yaml直接删除(推荐) kubectl delete -f pc-replicaset.yamlDeployment(Deploy) 为了更好的解决服务编排的问题kubernetes在V1.2版本开始引入了Deployment控制器。值得一提的是这种控制器并不直接管理pod而是通过管理ReplicaSet来简介管理Pod即Deployment管理ReplicaSetReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。 Deployment主要功能有下面几个 支持ReplicaSet的所有功能支持发布的停止、继续支持滚动更新和回滚版本 yaml配置模板 apiVersion: apps/v1 # 版本号 kind: Deployment # 类型 metadata: # 元数据name: # rs名称 namespace: # 所属命名空间 labels: #标签controller: deploy spec: # 详情描述replicas: 3 # 副本数量revisionHistoryLimit: 3 # 保留历史版本paused: false # 暂停部署默认是falseprogressDeadlineSeconds: 600 # 部署超时时间s默认是600strategy: # 策略type: RollingUpdate # 滚动更新策略rollingUpdate: # 滚动更新maxSurge: 30% # 最大额外可以存在的副本数可以为百分比也可以为整数maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值可以为百分比也可以为整数selector: # 选择器通过它指定该控制器管理哪些podmatchLabels: # Labels匹配规则app: nginx-podmatchExpressions: # Expressions匹配规则- {key: app, operator: In, values: [nginx-pod]}template: # 模板当副本数量不足时会根据下面的模板创建pod副本metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80创建 创建pc-deployment.yaml文件 apiVersion: apps/v1 kind: Deployment metadata:name: pc-deploymentnamespace: dev spec: replicas: 3selector:matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1执行命令测试 # 创建deployment kubectl create -f pc-deployment.yaml --recordtrue # 查看deployment # UP-TO-DATE 最新版本的pod的数量 # AVAILABLE 当前可用的pod的数量 kubectl get deploy pc-deployment -n dev # 查看rs # 发现rs的名称是在原来deployment的名字后面添加了一个10位数的随机串 kubectl get rs -n dev # 查看pod kubectl get pods -n dev扩缩容 #方式一 # 变更副本数量为5个 kubectl scale deploy pc-deployment --replicas5 -n dev # 查看deployment kubectl get deploy pc-deployment -n dev # 查看pod kubectl get pods -n dev#方式二 # 编辑deployment的副本数量修改spec:replicas: 4即可 kubectl edit deploy pc-deployment -n dev # 查看pod kubectl get pods -n dev镜像更新 deployment支持两种更新策略:重建更新和滚动更新,可以通过strategy指定策略类型,支持两个属性: strategy指定新的Pod替换旧的Pod的策略 支持两个属性type指定策略类型支持两种策略Recreate在创建出新的Pod之前会先杀掉所有已存在的PodRollingUpdate滚动更新就是杀死一部分就启动一部分在更新过程中存在两个版本PodrollingUpdate当type为RollingUpdate时生效用于为RollingUpdate设置参数支持两个属性maxUnavailable用来指定在升级过程中不可用Pod的最大数量默认为25%。maxSurge 用来指定在升级过程中可以超过期望的Pod的最大数量默认为25%。重建更新 在更新式重建更新策略会将原来运行的pod全部删除然后重新创建新的pod 编辑pc-deployment.yaml spec:strategy: # 策略type: Recreate # 重建更新执行命令测试 # 更新配置 kubectl apply -f pc-deployment.yaml # 观察升级过程(新开窗口) kubectl get pods -n dev -w # 变更镜像 kubectl set image deployment pc-deployment nginxnginx:1.17.2 -n dev滚动更新 在更新式滚动更新策略会根据配置的rollingUpdate先启动对应数量新的成功后停止对应数量的旧的不断重复直到所有旧的被替换 编辑pc-deployment.yaml spec:strategy: # 策略type: RollingUpdate # 滚动更新策略rollingUpdate:maxSurge: 25% maxUnavailable: 25%执行命令测试 # 更新配置 kubectl apply -f pc-deployment.yaml # 观察升级过程(新开窗口) kubectl get pods -n dev -w # 变更镜像 kubectl set image deployment pc-deployment nginxnginx:1.17.3 -n dev在镜像更新时通过kubectl get rs -n dev -w完成rs可以发现在每次更新时rs都创建一个新的rs并且创建新pod旧的rs的pod会被删除 之所以这样设计是为了后续的deployment版本回退当更新时出现问题只需要切换rs就能够实现版本回退 版本回退 deployment支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能下面具体来看. kubectl rollout 版本升级相关功能支持下面的选项 status 显示当前升级状态history 显示 升级历史记录pause 暂停版本升级过程resume 继续已经暂停的版本升级过程restart 重启版本升级过程undo 回滚到上一级版本可以使用–to-revision回滚到指定版本 # 查看当前升级版本的状态 kubectl rollout status deploy pc-deployment -n dev # 查看升级历史记录 kubectl rollout history deploy pc-deployment -n dev# 版本回滚 # 这里直接使用--to-revision1回滚到了1版本 如果省略这个选项就是回退到上个版本就是2版本 kubectl rollout undo deployment pc-deployment --to-revision1 -n dev # 查看发现通过nginx镜像版本可以发现到了第一版 kubectl get deploy -n dev -o wide # 查看rs发现第一个rs中有4个pod运行后面两个版本的rs中pod为运行 # 其实deployment之所以可是实现版本的回滚就是通过记录下历史rs来实现的 # 一旦想回滚到哪个版本只需要将当前版本pod数量降为0然后将回滚版本的pod提升为目标数量就可以了 kubectl get rs -n dev金丝雀发布 Deployment控制器支持控制更新过程中的控制如“暂停(pause)”或“继续(resume)”更新操作。 比如有一批新的Pod资源创建完成后立即暂停更新过程此时仅存在一部分新版本的应用主体部分还是旧的版本。然后再筛选一小部分的用户请求路由到新版本的Pod应用继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的Pod资源滚动更新否则立即回滚更新操作。这就是所谓的金丝雀发布。 # 更新deployment的版本并配置暂停deployment kubectl set image deploy pc-deployment nginxnginx:1.17.4 -n dev kubectl rollout pause deployment pc-deployment -n dev #观察更新状态 kubectl rollout status deploy pc-deployment -n dev  # 监控更新的过程可以看到已经新增了一个资源但是并未按照预期的状态去删除一个旧的资源就是因为使用了pause暂停命令 kubectl get rs -n dev -o wide kubectl get pods -n dev # 确保更新的pod没问题了继续更新 kubectl rollout resume deploy pc-deployment -n dev # 查看最后的更新情况 kubectl get rs -n dev -o wide kubectl get pods -n dev删除 kubectl delete -f pc-deployment.yamlHorizontal Pod Autoscaler(HPA) 在前面的课程种我们可以通过手工执行kubectl scale命令实现Pod扩容但是这显然不够智能Kubernetes期望可以通过检测Pod的使用情况实现pod数量的自动调整于是产生了HPA这种控制器 HPA可以获取每个pod利用率然后和HPA中设定的指标进行对比同时计算出需要伸缩的具体值最后实现pod的数量的调整其实HPA与之前的Deployment一样也属于一种Kubernetes资源对象它通过追踪分析目标pod的负载变化情况来确认是否需要针对性的调整目标pod的副本数 准备工作 安装metrics-server # 安装git yum install git -y # 获取metrics-server, 注意使用的版本 git clone -b v0.3.6 https://github.com/kubernetes-incubator/metrics-server # 修改deployment, 注意修改的是镜像和初始化参数 cd /root/metrics-server/deploy/1.8/ #添加下面选项 #hostNetwork: true #image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6 #args: #- --kubelet-insecure-tls #- --kubelet-preferred-address-typesInternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP vim metrics-server-deployment.yaml# 安装metrics-server kubectl apply -f ./ # 查看pod运行情况 kubectl get pod -n kube-system # 使用kubectl top node 查看资源使用情况 kubectl top nodes kubectl top pod -n kube-system准备deployment和servie 执行如下命令 # 使用命令模式创建deployment kubectl run nginx --imagenginx:1.17.1 --requestscpu100m -n dev # 创建service kubectl expose deployment nginx --typeNodePort --port80 -n dev # 查看 kubectl get deployment,pod,svc -n dev部署HPA 创建pc-hpa.yaml文件 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:name: pc-hpanamespace: dev spec:minReplicas: 1 #最小pod数量maxReplicas: 10 #最大pod数量targetCPUUtilizationPercentage: 3 # CPU使用率指标3%scaleTargetRef: # 指定要控制的nginx信息apiVersion: apps/v1kind: Deploymentname: nginx执行命令测试 # 创建hpa kubectl create -f pc-hpa.yaml # 查看hpa kubectl get hpa -n dev使用Jmeter进行压测可发现当压力增加后容器也不断增加最多增加10个 DaemonSet(DS) DaemonSet类型的控制器可以保证在集群中的每一台或指定节点上都运行一个副本一般适用于日志收集、节点监控等场景也就是说如果一个Pod提供的功能是节点级别的每个节点都需要且只需要一个那么这类Pod就适合使用DaemonSet类型的控制器创建。 DaemonSet控制器的特点 每当向集群中添加一个节点时指定的 Pod 副本也将添加到该节点上当节点从集群中移除时Pod 也就被垃圾回收了 DaemonSet的yaml配置清单 apiVersion: apps/v1 # 版本号 kind: DaemonSet # 类型 metadata: # 元数据name: # rs名称 namespace: # 所属命名空间 labels: #标签controller: daemonset spec: # 详情描述revisionHistoryLimit: 3 # 保留历史版本updateStrategy: # 更新策略type: RollingUpdate # 滚动更新策略rollingUpdate: # 滚动更新maxUnavailable: 1 # 最大不可用状态的 Pod 的最大值可以为百分比也可以为整数selector: # 选择器通过它指定该控制器管理哪些podmatchLabels: # Labels匹配规则app: nginx-podmatchExpressions: # Expressions匹配规则- {key: app, operator: In, values: [nginx-pod]}template: # 模板当副本数量不足时会根据下面的模板创建pod副本metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80创建 创建pc-daemonset.yaml apiVersion: apps/v1 kind: DaemonSet metadata:name: pc-daemonsetnamespace: dev spec: selector:matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1执行命令测试 # 创建daemonset kubectl create -f pc-daemonset.yaml # 查看daemonset kubectl get ds -n dev -o wide # 查看pod,发现在每个Node上都运行一个pod kubectl get pods -n dev -o wide # 删除daemonset kubectl delete -f pc-daemonset.yamlJob Job主要用于负责**批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)**任务。Job特点如下 当Job创建的pod执行成功结束时Job将记录成功结束的pod数量当成功结束的pod达到指定的数量时Job将完成执行 Job的yaml配置清单 apiVersion: batch/v1 # 版本号 kind: Job # 类型 metadata: # 元数据name: # rs名称 namespace: # 所属命名空间 labels: #标签controller: job spec: # 详情描述completions: 1 # 指定job需要成功运行Pods的次数。默认值: 1parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1activeDeadlineSeconds: 30 # 指定job可运行的时间期限超过时间还未结束系统将会尝试进行终止。backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6manualSelector: true # 是否可以使用selector选择器选择pod默认是falseselector: # 选择器通过它指定该控制器管理哪些podmatchLabels: # Labels匹配规则app: counter-podmatchExpressions: # Expressions匹配规则- {key: app, operator: In, values: [counter-pod]}template: # 模板当副本数量不足时会根据下面的模板创建pod副本metadata:labels:app: counter-podspec:restartPolicy: Never # 重启策略只能设置为Never或者OnFailurecontainers:- name: counterimage: busybox:1.30command: [bin/sh,-c,for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done]restartPolicy关于重启策略设置的说明 如果指定为OnFailure则job会在pod出现故障时重启容器而不是创建podfailed次数不变 如果指定为Never则job会在pod出现故障时创建新的pod并且故障pod不会消失也不会重启failed次数加1 如果指定为Always的话就意味着一直重启意味着job任务会重复去执行了当然不对所以不能设置为Always 创建 创建pc-job.yaml apiVersion: batch/v1 kind: Job metadata:name: pc-jobnamespace: dev spec:manualSelector: trueselector:matchLabels:app: counter-podtemplate:metadata:labels:app: counter-podspec:restartPolicy: Nevercontainers:- name: counterimage: busybox:1.30command: [bin/sh,-c,for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3;done]执行命令测试 # 创建job kubectl create -f pc-job.yaml # 查看job kubectl get job -n dev -o wide -w # 通过观察pod状态可以看到pod在运行完毕任务后就会变成Completed状态 kubectl get pods -n dev -w # 接下来调整下pod运行的总数量和并行数量 即在spec下设置下面两个选项 # completions: 6 # 指定job需要成功运行Pods的次数为6 # parallelism: 3 # 指定job并发运行Pods的数量为3 # 然后重新运行job观察效果此时会发现job会每次运行3个pod总共执行了6个pod kubectl get pods -n dev -w # 删除job kubectl delete -f pc-job.yamlCronjob CronJob控制器以Job控制器资源为其管控对象并借助它管理pod资源对象Job控制器定义的作业任务在其控制器资源创建之后便会立即执行但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说CronJob可以在特定的时间点(反复的)去运行job任务。 CronJob的yaml配置清单 apiVersion: batch/v1beta1 # 版本号 kind: CronJob # 类型 metadata: # 元数据name: # rs名称 namespace: # 所属命名空间 labels: #标签controller: cronjob spec: # 详情描述schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行concurrencyPolicy: # 并发执行策略用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数默认为1successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数默认为3startingDeadlineSeconds: # 启动作业错误的超时时长jobTemplate: # job控制器模板用于为cronjob控制器生成job对象;下面其实就是job的定义metadata:spec:completions: 1parallelism: 1activeDeadlineSeconds: 30backoffLimit: 6manualSelector: trueselector:matchLabels:app: counter-podmatchExpressions: 规则- {key: app, operator: In, values: [counter-pod]}template:metadata:labels:app: counter-podspec:restartPolicy: Never containers:- name: counterimage: busybox:1.30command: [bin/sh,-c,for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done]需要重点解释的几个选项 schedule: cron表达式用于指定任务的执行时间 concurrencyPolicy: Allow: 允许Jobs并发运行(默认) Forbid: 禁止并发运行如果上一次运行尚未完成则跳过下一次运行 Replace: 替换取消当前正在运行的作业并用新作业替换它 创建 创建pc-cronjob.yaml apiVersion: batch/v1beta1 kind: CronJob metadata:name: pc-cronjobnamespace: devlabels:controller: cronjob spec:schedule: */1 * * * *jobTemplate:metadata:spec:template:spec:restartPolicy: Nevercontainers:- name: counterimage: busybox:1.30command: [bin/sh,-c,for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3;done]执行命令测试 # 创建cronjob kubectl create -f pc-cronjob.yaml # 查看cronjob kubectl get cronjobs -n dev # 查看job kubectl get jobs -n dev # 查看pod kubectl get pods -n dev # 删除cronjob kubectl delete -f pc-cronjob.yamlService详解 Kubernetes流量负载组件Service(4层路由的负载)和Ingress(7层路由的负载) 在Kubernetes中pod是应用程序的载体通过访问pod的ip来访问程序但是pod的ip地址都是随机生成的为了解决这个问题Kubernetes提供了Service资源Service资源会对相同一个服务多个pod进行聚合并提供一个统一的入口地址通过Service的入口地址就能访问到后面的pod服务 Service在很多情况下只是一个概念真正起作用的其实是kube-proxy服务进程每个Node节点上都运行着一个Kube-proxy服务当创建Service的时候会通过api-Servee想etcd写入创建Service信息而kube-proxy会基于监听的机制发现这种Service的变动然后它会将最新的Service信息转换成对应的访问规则 10.97.97.97:80 是service提供的访问入口 当访问这个入口的时候可以发现后面有三个pod的服务在等待调用 kube-proxy会基于rr轮询的策略将请求分发到其中一个pod上去 这个规则会同时在集群内的所有节点上都生成所以在任何一个节点上访问都可以。 ipvsadm -Ln kube-proxy工作模式 userspace userspace模式下kube-proxy会为每一个Service创建一个监听端口发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接以将请求转发到Pod上。 该模式下kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中在进行转发处理时会增加内核和用户空间之间的数据拷贝虽然比较稳定但是效率比较低 iptables iptables模式下kube-proxy为service后端的每个Pod创建对应的iptables规则直接将发向Cluster IP的请求重定向到一个Pod IP。 该模式下kube-proxy不承担四层负责均衡器的角色只负责创建iptables规则。该模式的优点是较userspace模式效率更高但不能提供灵活的LB策略当后端Pod不可用时也无法进行重试。 ipvs ipvs模式和iptables类似kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外ipvs支持更多的LB算法 # 此模式必须安装ipvs内核模块否则会降级为iptables # 开启ipvs kubectl edit cm kube-proxy -n kube-system # 修改mode: ipvs kubectl delete pod -l k8s-appkube-proxy -n kube-system # 查看ipvs信息 ipvsadm -LnService类型 Service的yaml配置清单 kind: Service # 资源类型 apiVersion: v1 # 资源版本 metadata: # 元数据name: service # 资源名称namespace: dev # 命名空间 spec: # 描述selector: # 标签选择器用于确定当前service代理哪些podapp: nginxtype: # Service类型指定service的访问方式clusterIP: # 虚拟服务的ip地址sessionAffinity: # session亲和性支持ClientIP、None两个选项ports: # 端口信息- protocol: TCP port: 3017 # service端口targetPort: 5003 # pod端口nodePort: 31122 # 主机端口ClusterIP默认值它是Kubernetes系统自动分配的虚拟IP只能在集群内部访问NodePort将Service通过指定的Node上的端口暴露给外部通过此方法就可以在集群外部访问服务LoadBalancer使用外接负载均衡器完成到服务的负载分发注意此模式需要外部云环境支持ExternalName 把集群外部的服务引入集群内部直接使用 Service使用 环境准备 首先我们需要安装如下配置准备3个pod通过Service访问pod服务再通过切换不同类型的Service来测试不同类型的Service的区别 pod程序pod控制器pod暴露端口nignxdeploy80nignxdeploy80nignxdeploy80 创建deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:name: pc-deploymentnamespace: dev spec: replicas: 3selector:matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80执行命令 # 创建pod kubectl create -f deployment.yaml # 查看pod详情 kubectl get pods -n dev -o wide --show-labels在不同的机器上使用命令进入容器修改inde.heml页面内容方便后期测试时知道访问的是那个nginx # 为了方便后面的测试修改下三台nginx的index.html页面三台修改的IP地址不一致 kubectl exec -it pc-deployment-6696798b78-7lms6 -n dev /bin/sh echo nginx IP 10.244.2.43 /usr/share/nginx/html/index.html exitkubectl exec -it pc-deployment-6696798b78-f7w2g -n dev /bin/sh echo nginx IP 10.244.1.142 /usr/share/nginx/html/index.html exitkubectl exec -it pc-deployment-6696798b78-q9dqw -n dev /bin/sh echo nginx IP 10.244.1.141 /usr/share/nginx/html/index.html exit测试是否能够正常访问 curl 10.244.2.43 curl 10.244.1.142 curl 10.244.1.141ClusterIP类型Service 创建service-clusterip.yaml apiVersion: v1 kind: Service metadata:name: service-clusteripnamespace: dev spec:selector:app: nginx-podclusterIP: 10.97.97.97 # service的ip地址如果不写默认会生成一个type: ClusterIPports:- port: 80 # Service端口 targetPort: 80 # pod端口执行如下命令 # 创建service kubectl create -f service-clusterip.yaml # 查看service kubectl get svc -n dev -o wide # 查看service的详细信息 # 在这里有一个Endpoints列表里面就是当前service可以负载到的服务入口 kubectl describe svc service-clusterip -n dev测试查看kube-poxy是否创建了相应的ipvs规则 # 查看ipvs的映射规则 ipvsadm -Ln | grep 10.97.97.97 -A 3 # 访问10.97.97.97:80观察效果 curl 10.97.97.97:80负载分发策略 对Service的访问被分发到了后端的Pod上去目前kubernetes提供了两种负载分发策略 如果不定义默认使用kube-proxy的策略比如随机、轮询上面的rr就是轮询 基于客户端地址的会话保持模式即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上 此模式可以使在spec中添加sessionAffinity: ClientIP选项 #修改原有的配置文件 #添加sessionAffinity: ClientIP vim service-clusterip.yaml #应用配置文件 kubectl apply -f service-clusterip.yaml # 查看ipvs的映射规则 ipvsadm -Ln | grep 10.97.97.97 -A 3 # 访问10.97.97.97:80观察效果 curl 10.97.97.97:80ipvsadm可以看到在rr后面多了persistent 10800(持久化10800秒) 可以发现亲和性已改为ClientIP 通过curl发全球可以发现请求转发都是基于session的在有效期内都是访问的相同的nginx HeadLiness类型的Service 在某些场景中开发人员可能不想使用Service提供的负载均衡功能而希望自己来控制负载均衡策略针对这种情况kubernetes提供了HeadLiness Service这类Service不会分配Cluster IP如果想要访问service只能通过service的域名进行查询。 创建service-headliness.yaml apiVersion: v1 kind: Service metadata:name: service-headlinessnamespace: dev spec:selector:app: nginx-podclusterIP: None # 将clusterIP设置为None即可创建headliness Servicetype: ClusterIPports:- port: 80 targetPort: 80执行如下命令 # 创建service kubectl create -f service-headliness.yaml创建成功后因为通过域名访问就需要域名解析器随便进入一台pod查看以下域名解析器的地址 使用dig命令查看域名解析dig 10.96.0.10 service-headliness.dev.svc.cluster.local NodePort类型的Service 在之前的样例中创建的Service的ip地址只有集群内部才可以访问如果希望将Service暴露给集群外部使用那么就要使用到另外一种类型的Service称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上然后就可以通过NodeIp:NodePort来访问service了。 创建service-nodeport.yaml apiVersion: v1 kind: Service metadata:name: service-nodeportnamespace: dev spec:selector:app: nginx-podtype: NodePort # service类型ports:- port: 80nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是30000-32767), 如果不指定会默认分配targetPort: 80执行如下命令 # 创建service kubectl create -f service-nodeport.yaml # 查看service kubectl get svc -n dev -o wideLoadBalancer类型的Service LoadBalancer和NodePort很相似目的都是向外部暴露一个端口区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备而这个设备需要外部环境支持的外部服务发送到这个设备上的请求会被设备负载之后转发到集群中。 ExternalName类型的Service ExternalName类型的Service用于引入集群外部的服务它通过externalName属性指定外部一个服务的地址然后在集群内部访问此service就可以访问到外部的服务了。 apiVersion: v1 kind: Service metadata:name: service-externalnamenamespace: dev spec:type: ExternalName # service类型externalName: www.baidu.com #改成ip地址也可以# 创建service kubectl create -f service-externalname.yaml # 域名解析 dig 10.96.0.10 service-externalname.dev.svc.cluster.local service-externalname.dev.svc.cluster.local. 30 IN CNAME www.baidu.com.Ingress介绍 在前面课程中已经提到Service对集群之外暴露服务的主要方式有两种NotePort和LoadBalancer但是这两种方式都有一定的缺点 NodePort方式的缺点是会占用很多集群机器的端口那么当集群服务变多的时候这个缺点就愈发明显LB方式的缺点是每个service需要一个LB浪费、麻烦并且需要kubernetes之外设备的支持 基于这种现状kubernetes提供了Ingress资源对象Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图表示 实际上Ingress相当于一个7层的负载均衡器是kubernetes对反向代理的一个抽象它的工作原理类似于Nginx可以理解成在Ingress里建立诸多映射规则Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置 , 然后对外部提供服务。在这里有两个核心概念 ingresskubernetes中的一个对象作用是定义请求如何转发到service的规则ingress controller具体实现反向代理及负载均衡的程序对ingress定义的规则进行解析根据配置的规则来实现请求转发实现方式有很多比如Nginx, Contour, Haproxy等等 Ingress以Nginx为例的工作原理如下 用户编写Ingress规则说明哪个域名对应kubernetes集群中的哪个ServiceIngress控制器动态感知Ingress服务规则的变化然后生成一段对应的Nginx反向代理配置Ingress控制器会将生成的Nginx配置写入到一个运行着的Nginx服务中并动态更新到此为止其实真正在工作的就是一个Nginx了内部配置了用户定义的请求转发规则 环境准备 搭建ingress环境 # 创建文件夹 mkdir ingress-controller cd ingress-controller/ # 获取ingress-nginx本次案例使用的是0.30版本 wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml # 创建ingress-nginx kubectl apply -f ./ # 查看ingress-nginx kubectl get pod -n ingress-nginx # 查看service kubectl get svc -n ingress-nginx准备service和pod 为了接下来的实验我们准备如下实现模型 创建nginx-tomcat.yaml apiVersion: apps/v1 kind: Deployment metadata:name: nginx-deploymentnamespace: dev spec:replicas: 3selector:matchLabels:app: nginx-podtemplate:metadata:labels:app: nginx-podspec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80---apiVersion: apps/v1 kind: Deployment metadata:name: tomcat-deploymentnamespace: dev spec:replicas: 3selector:matchLabels:app: tomcat-podtemplate:metadata:labels:app: tomcat-podspec:containers:- name: tomcatimage: tomcat:8.5-jre10-slimports:- containerPort: 8080---apiVersion: v1 kind: Service metadata:name: nginx-servicenamespace: dev spec:selector:app: nginx-podclusterIP: Nonetype: ClusterIPports:- port: 80targetPort: 80---apiVersion: v1 kind: Service metadata:name: tomcat-servicenamespace: dev spec:selector:app: tomcat-podclusterIP: Nonetype: ClusterIPports:- port: 8080targetPort: 8080执行如下命令 # 创建 kubectl create -f nginx-tomcat.yaml # 查看 kubectl get svc -n devHttp代理 创建ingress-http.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata:name: ingress-httpnamespace: dev spec:rules:- host: nginx.test.comhttp:paths:- path: /backend:serviceName: nginx-serviceservicePort: 80- host: tomcat.test.comhttp:paths:- path: /backend:serviceName: tomcat-serviceservicePort: 8080执行如下命令 # 创建 kubectl create -f ingress-http.yaml # 查看 kubectl get ing ingress-http -n dev # 查看详情 kubectl describe ing ingress-http -n dev # 接下来,在本地电脑上配置host文件,解析上面的两个域名到192.168.10.100(master)上 # 然后,就可以分别访问tomcat.test.com:32519 和 nginx.test.com:32519 查看效果了Https代理 创建证书 # 生成证书 openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj /CCN/STBJ/LBJ/Onginx/CNtest.com # 创建密钥 kubectl create secret tls tls-secret --key tls.key --cert tls.crt创建ingress-https.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata:name: ingress-httpsnamespace: dev spec:tls:- hosts:- nginx.test.com- tomcat.test.comsecretName: tls-secret # 指定秘钥rules:- host: nginx.test.comhttp:paths:- path: /backend:serviceName: nginx-serviceservicePort: 80- host: tomcat.test.comhttp:paths:- path: /backend:serviceName: tomcat-serviceservicePort: 8080执行如下命令 # 创建 kubectl create -f ingress-https.yaml # 查看 kubectl get ing ingress-https -n dev # 查看详情 kubectl describe ing ingress-https -n dev数据存储 在前面已经提到容器的生命周期可能很短会被频繁地创建和销毁。那么容器在销毁时保存在容器中的数据也会被清除。这种结果对用户来说在某些情况下是不乐意看到的。为了持久化保存容器的数据kubernetes引入了Volume的概念。 Volume是Pod中能够被多个容器访问的共享目录它被定义在Pod上然后被一个Pod里的多个容器挂载到具体的文件目录下kubernetes通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命容器不与Pod中单个容器的生命周期相关当容器终止或者重启时Volume中的数据也不会丢失。 kubernetes的Volume支持多种类型比较常见的有下面几个 简单存储EmptyDir、HostPath、NFS高级存储PV、PVC配置存储ConfigMap、Secret 基础存储 EmptyDir EmptyDir是最基础的Volume类型一个EmptyDir就是Host上的一个空目录EmptyDir是在Pod被分配到Node时创建的它的初始内容为空并且无须指定宿主机上对应的目录文件因为Kubernetes会自动分配一个目录当Pod销毁时EmptyDir中的数据也会被永久删除 EmptyDir用途如下 临时空间例如用于某些应用程序运行时所需的临时目录且无须永久保留一个容器需要从另一个容器中获取数据的目录多容器共享目录 案例演示 接下来通过一个容器之间文件共享的案例来使用一下EmptyDir。 在一个Pod中准备两个容器nginx和busybox然后声明一个Volume分别挂在到两个容器的目录中然后nginx容器负责向Volume中写日志busybox中通过命令将日志内容读到控制台。 创建一个volume-emptydir.yaml apiVersion: v1 kind: Pod metadata:name: volume-emptydirnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80volumeMounts: # 将logs-volume挂在到nginx容器中对应的目录为 /var/log/nginx- name: logs-volumemountPath: /var/log/nginx- name: busyboximage: busybox:1.30command: [/bin/sh,-c,tail -f /logs/access.log] # 初始命令动态读取指定文件中内容volumeMounts: # 将logs-volume 挂在到busybox容器中对应的目录为 /logs- name: logs-volumemountPath: /logsvolumes: # 声明volume name为logs-volume类型为emptyDir- name: logs-volumeemptyDir: {}执行如下命令 # 创建Pod kubectl create -f volume-emptydir.yaml # 查看pod kubectl get pods volume-emptydir -n dev -o wide # 通过podIp访问nginx curl 10.244.1.150 # 通过kubectl logs命令查看指定容器的标准输出 kubectl logs -f volume-emptydir -n dev -c busyboxHostPath 前面提到的EmptyDir中数据不会被持久化它会随着Pod的结束而销毁如果想简单的将数据持久化到主机中可以选择HostPath。 HostPath就是将Node主机中一个实际目录挂在到Pod中以供容器使用这样的设计就可以保证Pod销毁了但是数据依据可以存在于Node主机上。 创建一个volume-hostpath.yaml apiVersion: v1 kind: Pod metadata:name: volume-hostpathnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80volumeMounts:- name: logs-volumemountPath: /var/log/nginx- name: busyboximage: busybox:1.30command: [/bin/sh,-c,tail -f /logs/access.log]volumeMounts:- name: logs-volumemountPath: /logsvolumes:- name: logs-volumehostPath: path: /root/logstype: DirectoryOrCreate # 目录存在就使用不存在就先创建后使用关于type的值的一点说明 类型说明DirectoryOrCreate目录存在就使用不存在就先创建后使用Directory目录必须存在FileOrCreate文件存在就使用不存在就先创建后使用File文件必须存在Socketunix套接字必须存在CharDevice字符设备必须存在BlockDevice块设备必须存在 执行如下代码 # 创建Pod kubectl create -f volume-hostpath.yaml # 查看Pod kubectl get pods volume-hostpath -n dev -o wide #访问nginx curl 10.244.2.51 # 接下来就可以去host的/root/logs目录下查看存储的文件了 ### 注意: 下面的操作需要到Pod所在的节点运行案例中是node1 ls /root/logs/ # 同样的道理如果在此目录下创建一个文件到容器中也是可以看到的NFS HostPath可以解决数据持久化的问题但是一旦Node节点故障了Pod如果转移到了别的节点又会出现问题了此时需要准备单独的网络存储系统比较常用的用NFS、CIFS。 NFS是一个网络文件存储系统可以搭建一台NFS服务器然后将Pod中的存储直接连接到NFS系统上这样的话无论Pod在节点上怎么转移只要Node跟NFS的对接没问题数据就可以成功访问。 准备nsf服务器 这里为了方便测试直接将master作为nfs服务器 # 在nfs上安装nfs服务 yum install nfs-utils -y # 准备一个共享目录 mkdir /root/data/nfs -pv # 编辑/etc/exports文件(nfs默认读取的一个配置文件) # 添加内容如下 /root/data/nfs 192.168.10.0/24(rw,no_root_squash) # 将共享目录以读写权限暴露给192.168.10.0/24网段中的所有主机 vim /etc/exports more /etc/exports # 启动nfs服务 systemctl restart nfs在node上安装nfs工具 # 在node上安装nfs服务注意不需要启动 yum install nfs-utils -y创建pod配置文件 创建volume-nfs.yaml apiVersion: v1 kind: Pod metadata:name: volume-nfsnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1ports:- containerPort: 80volumeMounts:- name: logs-volumemountPath: /var/log/nginx- name: busyboximage: busybox:1.30command: [/bin/sh,-c,tail -f /logs/access.log] volumeMounts:- name: logs-volumemountPath: /logsvolumes:- name: logs-volumenfs:server: 192.168.10.100 #nfs服务器地址path: /root/data/nfs #共享文件路径执行如下命令 # 创建pod kubectl create -f volume-nfs.yaml # 查看pod kubectl get pods volume-nfs -n dev # 查看nfs服务器上的共享目录发现已经有文件了 ls /root/data/nfs高级存储 前面已经学习了使用NFS提供存储此时就要求用户会搭建NFS系统并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多要求客户全都掌握显然不现实。为了能够屏蔽底层存储实现的细节方便用户使用 kubernetes引入PV和PVC两种资源对象。 PVPersistent Volume是持久化卷的意思是对底层的共享存储的一种抽象。一般情况下PV由kubernetes管理员进行创建和配置它与底层具体的共享存储技术有关并通过插件完成与共享存储的对接。 PVCPersistent Volume Claim是持久卷声明的意思是用户对于存储需求的一种声明。换句话说PVC其实就是用户向kubernetes系统发出的一种资源需求申请。 使用了PV和PVC之后工作可以得到进一步的细分 存储存储工程师维护PV kubernetes管理员维护PVCkubernetes用户维护 PV PV的yaml配置清单 apiVersion: v1 kind: PersistentVolume metadata:name: pv2 spec:nfs: # 存储类型与底层真正存储对应capacity: # 存储能力目前只支持存储空间的设置storage: 2GiaccessModes: # 访问模式storageClassName: # 存储类别persistentVolumeReclaimPolicy: # 回收策略参数说明 存储类型 底层实际存储的类型kubernetes支持多种存储类型每种存储类型的配置都有所差异 存储能力capacity 目前只支持存储空间的设置( storage1Gi )不过未来可能会加入IOPS、吞吐量等指标的配置访问模式accessModes 用于描述用户应用对存储资源的访问权限访问权限包括下面几种方式 ReadWriteOnceRWO读写权限但是只能被单个节点挂载ReadOnlyManyROX 只读权限可以被多个节点挂载ReadWriteManyRWX读写权限可以被多个节点挂载 需要注意的是底层不同的存储类型可能支持的访问模式不同 回收策略persistentVolumeReclaimPolicy 当PV不再被使用了之后对其的处理方式。目前支持三种策略 Retain 保留 保留数据需要管理员手工清理数据Recycle回收 清除 PV 中的数据效果相当于执行 rm -rf /thevolume/*Delete 删除 与 PV 相连的后端存储完成 volume 的删除操作当然这常见于云服务商的存储服务 需要注意的是底层不同的存储类型可能支持的回收策略不同 存储类别 PV可以通过storageClassName参数指定一个存储类别 具有特定类别的PV只能与请求了该类别的PVC进行绑定未设定类别的PV则只能与不请求任何类别的PVC进行绑定 状态status 一个 PV 的生命周期中可能会处于4中不同的阶段 Available可用 表示可用状态还未被任何 PVC 绑定Bound已绑定 表示 PV 已经被 PVC 绑定Released已释放 表示 PVC 被删除但是资源还未被集群重新声明Failed失败 表示该 PV 的自动回收失败 创建PV 以下使用nfs作为存储演示PV的使用创建3个PV对应NFS中的3个暴露的路径 环境准备 # 创建目录 mkdir /root/data/{pv1,pv2,pv3} -pv # 暴露服务,在/etc/exports添加如下内容 #/root/data/pv1 192.168.10.0/24(rw,no_root_squash) #/root/data/pv2 192.168.10.0/24(rw,no_root_squash) #/root/data/pv3 192.168.10.0/24(rw,no_root_squash) vim /etc/exports # 重启服务 systemctl restart nfs创建yaml 创建pv.yaml apiVersion: v1 kind: PersistentVolume metadata:name: pv1 spec:capacity: storage: 1GiaccessModes:- ReadWriteManypersistentVolumeReclaimPolicy: Retainnfs:path: /root/data/pv1server: 192.168.10.100---apiVersion: v1 kind: PersistentVolume metadata:name: pv2 spec:capacity: storage: 2GiaccessModes:- ReadWriteManypersistentVolumeReclaimPolicy: Retainnfs:path: /root/data/pv2server: 192.168.10.100---apiVersion: v1 kind: PersistentVolume metadata:name: pv3 spec:capacity: storage: 3GiaccessModes:- ReadWriteManypersistentVolumeReclaimPolicy: Retainnfs:path: /root/data/pv3server: 192.168.10.100执行如下命令 # 创建 pv kubectl create -f pv.yaml # 查看pv kubectl get pv -o widePVC PVC是对管理员创建好的PV资源的申请用来声明对存储空间、访问模式、存储类别需求信息 PVC的yaml配置清单 apiVersion: v1 kind: PersistentVolumeClaim metadata:name: pvcnamespace: dev spec:accessModes: # 访问模式selector: # 采用标签对PV选择storageClassName: # 存储类别resources: # 请求空间requests:storage: 5Gi参数说明 访问模式accessModes 用于描述用户应用对存储资源的访问权限 选择条件selector 通过Label Selector的设置可使PVC对于系统中己存在的PV进行筛选 存储类别storageClassName PVC在定义时可以设定需要的后端存储的类别只有设置了该class的pv才能被系统选出 资源请求Resources 描述对存储资源的请求 PV绑定PVC 创建pvc.yaml申请PV apiVersion: v1 kind: PersistentVolumeClaim metadata:name: pvc1namespace: dev spec:accessModes: - ReadWriteManyresources:requests:storage: 1Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata:name: pvc2namespace: dev spec:accessModes: - ReadWriteManyresources:requests:storage: 1Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata:name: pvc3namespace: dev spec:accessModes: - ReadWriteManyresources:requests:storage: 1Gi# 创建pvc kubectl create -f pvc.yaml # 查看pvc kubectl get pvc -n dev -o wide # 查看pv kubectl get pv -o widePod使用PV 创建pods.yaml apiVersion: v1 kind: Pod metadata:name: pod1namespace: dev spec:containers:- name: busyboximage: busybox:1.30command: [/bin/sh,-c,while true;do echo pod1 /root/out.txt; sleep 10; done;]volumeMounts:- name: volumemountPath: /root/volumes:- name: volumepersistentVolumeClaim:claimName: pvc1readOnly: false --- apiVersion: v1 kind: Pod metadata:name: pod2namespace: dev spec:containers:- name: busyboximage: busybox:1.30command: [/bin/sh,-c,while true;do echo pod2 /root/out.txt; sleep 10; done;]volumeMounts:- name: volumemountPath: /root/volumes:- name: volumepersistentVolumeClaim:claimName: pvc2readOnly: false执行如下命令 # 创建pod kubectl create -f pods.yaml # 查看pod kubectl get pods -n dev -o wide # 查看pvc kubectl get pvc -n dev -o wide # 查看pv kubectl get pv -n dev -o wide # 查看nfs中的文件存储 more /root/data/pv1/out.txt more /root/data/pv2/out.txt生命周期 PVC和PV是一一对应的PV和PVC之间的相互作用遵循以下生命周期 资源供应管理员手动创建底层存储和PV 资源绑定用户创建PVCkubernetes负责根据PVC的声明去寻找PV并绑定 在用户定义好PVC之后系统将根据PVC对存储资源的请求在已存在的PV中选择一个满足条件的 一旦找到就将该PV与用户定义的PVC进行绑定用户的应用就可以使用这个PVC了如果找不到PVC则会无限期处于Pending状态直到等到系统管理员创建了一个符合其要求的PV PV一旦绑定到某个PVC上就会被这个PVC独占不能再与其他PVC进行绑定了 资源使用用户可在pod中像volume一样使用pvc Pod使用Volume的定义将PVC挂载到容器内的某个路径进行使用。 资源释放用户删除pvc来释放pv 当存储资源使用完毕后用户可以删除PVC与该PVC绑定的PV将会被标记为“已释放”但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还被留在存储设备上只有在清除之后该PV才能再次使用。 资源回收kubernetes根据pv设置的回收策略进行资源的回收 对于PV管理员可以设定回收策略用于设置与之绑定的PVC释放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收才能供新的PVC绑定和使用 配置存储 ConfigMap ConfigMap是一种比较特殊的存储卷它的主要作用是用来存储配置信息的 创建configmap.yaml apiVersion: v1 kind: ConfigMap metadata:name: configmapnamespace: dev data:info: |username:adminpassword:123456接下来使用此配置文件创建configmap # 创建configmap kubectl create -f configmap.yaml # 查看configmap详情 kubectl describe cm configmap -n dev接下来创建一个pod-configmap.yaml将上面创建的configmap挂载进去 apiVersion: v1 kind: Pod metadata:name: pod-configmapnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1volumeMounts: # 将configmap挂载到目录- name: configmountPath: /configmap/configvolumes: # 引用configmap- name: configconfigMap:name: configmap执行如下命令 # 创建pod kubectl create -f pod-configmap.yaml # 查看pod kubectl get pod pod-configmap -n dev #进入容器 执行如下命令 more /configmap/config/info kubectl exec -it pod-configmap -n dev /bin/sh # 可以看到映射已经成功每个configmap都映射成了一个目录 # key---文件 value----文件中的内容 # 此时如果更新configmap的内容, 容器中的值也会动态更新Secret 在kubernetes中还存在一种和ConfigMap非常类似的对象称为Secret对象。它主要用于存储敏感信息例如密码、秘钥、证书等等 首先使用base64对数据进行编码 echo -n admin | base64 #准备username YWRtaW4 echo -n 123456 | base64 #准备password MTIzNDU2接下来编写secret.yaml并创建Secret apiVersion: v1 kind: Secret metadata:name: secretnamespace: dev type: Opaque data:username: YWRtaW4password: MTIzNDU2执行如下命令 # 创建secret kubectl create -f secret.yaml # 查看secret详情 kubectl describe secret secret -n dev创建pod-secret.yaml将上面创建的secret挂载进去 apiVersion: v1 kind: Pod metadata:name: pod-secretnamespace: dev spec:containers:- name: nginximage: nginx:1.17.1volumeMounts: # 将secret挂载到目录- name: configmountPath: /secret/configvolumes:- name: configsecret:secretName: secret执行如下命令 # 创建pod kubectl create -f pod-secret.yaml # 查看pod kubectl get pod pod-secret -n dev # 进入容器查看secret信息发现已经自动解码了 # more /secret/config/username more /secret/config/password kubectl exec -it pod-secret /bin/sh -n dev安全认证 Kubernetes作为一个分布式集群的管理工具保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对Kubernetes的各种客户端进行认证和鉴权操作 客户端 在Kubernetes集群中客户端通常有两类 User Account一般是独立于kubernetes之外的其他服务管理的用户账号。Service Accountkubernetes管理的账号用于为Pod中的服务进程在访问Kubernetes时提供身份标识。 认证、授权与准入控制 ApiServer是访问及管理资源对象的唯一入口。任何一个请求访问ApiServer都要经过下面三个流程 Authentication认证身份鉴别只有正确的账号才能够通过认证Authorization授权 判断用户是否有权限对访问的资源执行特定的动作Admission Control准入控制用于补充授权机制以实现更加精细的访问控制功能。 认证管理 Kubernetes集群安全的最关键点在于如何识别并认证客户端身份它提供了3种客户端身份认证方式 HTTP Base认证通过用户名密码的方式认证 这种认证方式是把“用户名:密码”用BASE64算法进行编码后的字符串放在HTTP请求中的Header Authorization域里发送给服务端。服务端收到后进行解码获取用户名及密码然后进行用户身份认证的过程。 HTTP Token认证通过一个Token来识别合法用户 这种认证方式是用一个很长的难以被模仿的字符串–Token来表明客户身份的一种方式。每个Token对应一个用户名当客户端发起API调用请求时需要在HTTP Header里放入TokenAPI Server接到Token后会跟服务器中保存的token进行比对然后进行用户身份认证的过程。 HTTPS证书认证基于CA根证书签名的双向数字证书认证方式 这种认证方式是安全性最高的一种方式但是同时也是操作起来最麻烦的一种方式。 HTTPS认证大体分为3个过程 证书申请和下发 HTTPS通信双方的服务器向CA机构申请证书CA机构下发根证书、服务端证书及私钥给申请者客户端和服务端的双向认证 1 客户端向服务器端发起请求服务端下发自己的证书给客户端客户端接收到证书后通过私钥解密证书在证书中获得服务端的公钥客户端利用服务器端的公钥认证证书中的信息如果一致则认可这个服务器2 客户端发送自己的证书给服务器端服务端接收到证书后通过私钥解密证书在证书中获得客户端的公钥并用该公钥认证证书信息确认客户端是否合法服务器端和客户端进行通信 服务器端和客户端协商好加密方案后客户端会产生一个随机的秘钥并加密然后发送到服务器端。服务器端接收这个秘钥后双方接下来通信的所有内容都通过该随机秘钥加密注意: Kubernetes允许同时配置多种认证方式只要其中任意一个方式认证通过即可 授权管理 授权发生在认证成功之后通过认证就可以知道请求用户是谁 然后Kubernetes会根据事先定义的授权策略来决定用户是否有权限访问这个过程就称为授权。 每个发送到ApiServer的请求都带上了用户和资源的信息比如发送请求的用户、请求的路径、请求的动作等授权就是根据这些信息和授权策略进行比较如果符合策略则认为授权通过否则会返回错误。 API Server目前支持以下几种授权策略 AlwaysDeny表示拒绝所有请求一般用于测试AlwaysAllow允许接收所有请求相当于集群不需要授权流程Kubernetes默认的策略ABAC基于属性的访问控制表示使用用户配置的授权规则对用户请求进行匹配和控制Webhook通过调用外部REST服务对用户进行授权Node是一种专用模式用于对kubelet发出的请求进行访问控制RBAC基于角色的访问控制kubeadm安装方式下的默认选项 RBAC(Role-Based Access Control) 基于角色的访问控制主要是在描述一件事情给哪些对象授予了哪些权限 其中涉及到了下面几个概念 对象User、Groups、ServiceAccount角色代表着一组定义在资源上的可操作动作(权限)的集合绑定将定义好的角色跟用户绑定在一起 RBAC引入了4个顶级资源对象 Role(ns级别)、ClusterRole(集群级别)角色用于指定一组权限RoleBinding(ns级别)、ClusterRoleBinding(集群级别)角色绑定用于将角色权限赋予给对象 Role、ClusterRole 一个角色就是一组权限的集合这里的权限都是许可形式的白名单 # Role只能对命名空间内的资源进行授权需要指定nameapce kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:namespace: devname: authorization-role rules: - apiGroups: [] # 支持的API组列表, 空字符串表示核心API群resources: [pods] # 支持的资源对象列表verbs: [get, watch, list] # 允许的对资源对象的操作方法列表# ClusterRole可以对集群范围内资源、跨namespaces的范围资源、非资源类型进行授权 kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:name: authorization-clusterrole rules: - apiGroups: []resources: [pods]verbs: [get, watch, list]需要详细说明的是rules中的参数 apiGroups: 支持的API组列表 ,apps, autoscaling, batchresources支持的资源对象列表 services, endpoints, pods,secrets,configmaps,crontabs,deployments,jobs, nodes,rolebindings,clusterroles,daemonsets,replicasets,statefulsets, horizontalpodautoscalers,replicationcontrollers,cronjobsverbs对资源对象的操作方法列表 get, list, watch, create, update, patch, delete, execRoleBinding、ClusterRoleBinding 角色绑定用来把一个角色绑定到一个目标对象上绑定目标可以是User、Group或者ServiceAccount # RoleBinding可以将同一namespace中的subject绑定到某个Role下则此subject即具有该Role定义的权限 kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:name: authorization-role-bindingnamespace: dev subjects: - kind: Username: testapiGroup: rbac.authorization.k8s.io roleRef:kind: Rolename: authorization-roleapiGroup: rbac.authorization.k8s.io# ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定授予权限 kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:name: authorization-clusterrole-binding subjects: - kind: Username: testapiGroup: rbac.authorization.k8s.io roleRef:kind: ClusterRolename: authorization-clusterroleapiGroup: rbac.authorization.k8s.ioRoleBinding引用ClusterRole进行授权 RoleBinding可以引用ClusterRole对属于同一命名空间内ClusterRole定义的资源主体进行授权。 一种很常用的做法就是集群管理员为集群范围预定义好一组角色ClusterRole然后在多个命名空间中重复使用这些ClusterRole。这样可以大幅提高授权管理工作效率也使得各个命名空间下的基础性授权规则与使用体验保持一致。 # 虽然authorization-clusterrole是一个集群角色但是因为使用了RoleBinding # 所以test只能读取dev命名空间中的资源 kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:name: authorization-role-binding-nsnamespace: dev subjects: - kind: Username: testapiGroup: rbac.authorization.k8s.io roleRef:kind: ClusterRolename: authorization-clusterroleapiGroup: rbac.authorization.k8s.io案例 在前面演示时我们都使用的是kubernetes提供的默认账号adminadmin账号权限极高能够操作所有的命名空间在企业中开发中通常需要缩减权限只需要给开发人员配置只能操作相应的命名空间即可 创建账号 # 1) 创建证书 cd /etc/kubernetes/pki/ (umask 077;openssl genrsa -out devman.key 2048)# 2) 用apiserver的证书去签署 # 2-1) 签名申请申请的用户是devman,组是devgroup openssl req -new -key devman.key -out devman.csr -subj /CNdevman/Odevgroup # 2-2) 签署证书 openssl x509 -req -in devman.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out devman.crt -days 3650# 3) 设置集群、用户、上下文信息 kubectl config set-cluster kubernetes --embed-certstrue --certificate-authority/etc/kubernetes/pki/ca.crt --serverhttps://192.168.10.100:6443kubectl config set-credentials devman --embed-certstrue --client-certificate/etc/kubernetes/pki/devman.crt --client-key/etc/kubernetes/pki/devman.keykubectl config set-context devmankubernetes --clusterkubernetes --userdevman# 切换账户到devman kubectl config use-context devmankubernetes# 查看dev下pod发现没有权限 kubectl get pods -n dev# 切换到admin账户 kubectl config use-context kubernetes-adminkubernetes创建Role和RoleBinding为devman用户授权 创建dev-role.yaml kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:namespace: devname: dev-role rules: - apiGroups: []resources: [pods]verbs: [get, watch, list]---kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata:name: authorization-role-bindingnamespace: dev subjects: - kind: Username: devmanapiGroup: rbac.authorization.k8s.io roleRef:kind: Rolename: dev-roleapiGroup: rbac.authorization.k8s.io执行如下命令 kubectl create -f dev-role.yaml测试 # 切换账户到devman kubectl config use-context devmankubernetes # 再次查看 kubectl get pods -n dev # 为了不影响后面的学习,切回admin账户 kubectl config use-context kubernetes-adminkubernetes准入控制 通过了前面的认证和授权之后还需要经过准入控制处理通过之后apiserver才会处理这个请求。 准入控制是一个可配置的控制器列表可以通过在Api-Server上通过命令行设置选择执行哪些准入控制器 --admission-controlNamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds只有当所有的准入控制器都检查通过之后apiserver才执行该请求否则返回拒绝。 当前可配置的Admission Control准入控制如下 AlwaysAdmit允许所有请求AlwaysDeny禁止所有请求一般用于测试AlwaysPullImages在启动容器之前总去下载镜像DenyExecOnPrivileged它会拦截所有想在Privileged Container上执行命令的请求ImagePolicyWebhook这个插件将允许后端的一个Webhook程序来完成admission controller的功能。Service Account实现ServiceAccount实现了自动化SecurityContextDeny这个插件将使用SecurityContext的Pod中的定义全部失效ResourceQuota用于资源配额管理目的观察所有请求确保在namespace上的配额不会超标LimitRanger用于资源限制管理作用于namespace上确保对Pod进行资源限制InitialResources为未设置资源请求与限制的Pod根据其镜像的历史资源的使用情况进行设置NamespaceLifecycle如果尝试在一个不存在的namespace中创建资源对象则该创建请求将被拒绝。当删除一个namespace时系统将会删除该namespace中所有对象。DefaultStorageClass为了实现共享存储的动态供应为未指定StorageClass或PV的PVC尝试匹配默认的StorageClass尽可能减少用户在申请PVC时所需了解的后端存储细节DefaultTolerationSeconds这个插件为那些没有设置forgiveness tolerations并具有notready:NoExecute和unreachable:NoExecute两种taints的Pod设置默认的“容忍”时间为5minPodSecurityPolicy这个插件用于在创建或修改Pod时决定是否根据Pod的security context和可用的PodSecurityPolicy对Pod的安全策略进行控制 DashBoard 之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实为了提供更丰富的用户体验kubernetes还开发了一个基于web的用户界面Dashboard。用户可以使用Dashboard部署容器化的应用还可以监控应用的状态执行故障排查以及管理kubernetes中各种资源。 部署Dashboard # 下载yaml wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml # 修改kubernetes-dashboard的Service类型 #kind: Service #apiVersion: v1 #metadata: # labels: # k8s-app: kubernetes-dashboard # name: kubernetes-dashboard # namespace: kubernetes-dashboard #spec: # type: NodePort # 新增 # ports: # - port: 443 # targetPort: 8443 # nodePort: 30009 # 新增 # selector: # k8s-app: kubernetes-dashboard # 部署 kubectl create -f recommended.yaml # 查看namespace下的kubernetes-dashboard下的资源 kubectl get pod,svc -n kubernetes-dashboard注意这里使用浏览器要使用火狐用谷歌浏览器可能会有问题 创建访问账户获取token # 创建账号 kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard # 授权 kubectl create clusterrolebinding dashboard-admin-rb --clusterrolecluster-admin --serviceaccountkubernetes-dashboard:dashboard-admin # 查看相关密钥 kubectl get secrets -n kubernetes-dashboard | grep dashboard-admin # 使用describe查看密钥中的token内容 kubectl describe secrets dashboard-admin-token-xtlcw -n kubernetes-dashboard登录成功后即可看到kubernetes的信息
http://www.ihoyoo.com/news/94374.html

相关文章:

  • 行政部网站建设规划去长沙旅游攻略
  • 郑州墨守网络网站建设响应式网站建设合同
  • 网站建设案例实录一个网站备案多个域名
  • 怎么样制作自己的网站贵阳官网seo诊断
  • 好的品牌设计网站网站建设 小知识
  • 福建志佳建设工程发展有限公司网站wordpress获取子分类
  • 一流的高密做网站的建设网站要多久到账
  • 网站建设维护杭州如何查看一个网站的域名解析
  • 网站建站公司服务好吗网站接入商查询
  • 东莞网站设计制作公司wordpress的搭建环境搭建
  • 上海网站建设公司网站建设做网站什么内容吸引人
  • 网站建设专题会议租用微信做拍卖网站
  • 网站制作全过程做IPv6网站升级的公司有哪些
  • 下列关于网站开发中网页上传和wordpress 页面评论 调用
  • 柯林wap建站程序个人版成品播放器
  • 有做游戏广告的网站wordpress 本地数据库
  • 设计网站属于什么专业关于网站建设的小故事
  • 入门做网站aws wordpress区别
  • 素材网站 模板为什么手机进网站乱码
  • 网站建设郑州百度怎样建立网站链接
  • 建设银行网站网址是什么南阳网站优化软件
  • 关于文化馆网站建设的材料网站信息平台建设方案
  • 网站用橙色name域名注册
  • 网站建设管理教程视频免费网站友情链接
  • 温州网站建设钱做网站弄关键词多少钱
  • 织梦做的相亲网站网站红色
  • 从零学习做网站nginx 做网站
  • 三亚做网站服务电子商务营销的发展趋势
  • 旅游精品网站建设企业融资以什么为基础
  • vc 做网站源码西安seo外包服务