赞
踩
# Kubernetes权威指南
《Kubernetes权威指南》第5版勘误:https://github.com/kubeguide/K8sDefinitiveGuide-V5-corrigendum
《Kubernetes权威指南》第5版源码:https://github.com/kubeguide/K8sDefinitiveGuide-V5-Sourcecode
namespace六种隔离能力:
根据前面对 Service 的使用说明,我们知道 Service 的表现形式为IP:Port,工作在TCP/IP层,而对于基于 HTTP 的服务来说,不同的URL地址经常对应到不同的后端服务或者虚拟服务器,这些应用层的转发机制仅通过kubernetes的Service机制是无法实现的。kubernetes V1.1版本中新增Ingress将不同URL的访问请求转发到后端不同的Service,以实现HTTP层的业务路由机制。
Ingress由两部分组成:Ingress Controller 和 Ingress 服务。需要注意的是,ingress 只能以 HTTP 和 HTTPS 提供服务,对使用其他网络协议的服务,可以通过设置 Service 的类型(type)为 NodePort 或 LoadBalancer 对集群外的客户端提供服务。
使用 Ingress 进行服务时,Ingress Controller 基于 Ingress 规则将客户端的请求直接转发到 Service 对应的后端 Endpoint(Pod)上,这样会跳过 kube-proxy 设置的路由转发规则,以提高网络转发效率。
图 4.7 显示了一个典型的 HTTP 层路由的例子:
其中:
对 https://mywebsite.com/api的访问将被路由到后端名为"api"的 Service。
对 https://mywebsite.com/web的访问将被路由到后端名为"web"的 Service。
对 https://mywebsite.com/doc的访问将被路由到后端名为"doc"的 Service。
下面通过一个完整的例子对 Ingress Controller 的部署、Ingress 策略配置,以及客户端如何通过 Ingress Controller 访问服务对 Ingress 的原理和应用进行说明。
# 一个完整的例子(ingress Controller+Ingress 策略+服务端访问) 1.部署ingress Controller ingress Controller 需要实现基于 HTTP URL 向后转发的负载分发规则,并可以灵活设置 7 层负载分发策略。目前 ingress Controller 已经有许多实现方案,包括 Nginx、HAproxy、Kong、Traefik、Skipper、Istio 等开源软件的实现,以及公有云 GCE、Azure、AWS 等提供的 Ingress 应用网关,用户可以参考官网根据业务需求选择合适的 ingress Controller。 在 K8S 中,ingress Controller 会持续监控 API Server 的/ingress 接口(即用户定义的到后端服务的转发规则)的变化。当/ingress 接口后端的服务信息发生变化时,ingress Controller 会自动更新其转发规则。 本例基于Nginx 提供的 Ingress Controller进行说明。Nginx Ingress Controller 可以以Daemonset 或 Deployment模式进行部署,通常可以考虑通过设置 nodeSelector或亲和性调度策略将其调度到固定的几个Node上提供服务。 对于客户端应用如何通过网络访问 Ingress Controller,本例中通过在容器级别设置hostPort,将80和443端口号映射到宿主机上,这样客户端应用可以通过 URL 地址“http://<NodeIP>:80”或“https://<NodeIP>:443”访问 Ingress Controller.也可以配置Pod使用 hostNetwork 模式直接监听宿主机网卡的IP地址和端口号,或者使用 Service的NodePort将端口号映射到宿主机上。 下面是Nginx Ingress Controller的YAML定义,其中将Pod创建在 namespace “nginx-ingress”中,通过 nodeSelector “role-ingress-nginx-controller”设置了调度的目标Node,并设置了 hostPort 将端口号映射到宿主机上供集群外部的客户端访问。 Namespace 的定义如下: --- apiVersion: v1 kind: Namespace metadata: name: nginx-ingress ServiceAccount 的定义如下: ###Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。service account,顾名思义,主要是给service使用的一个账号。具体一点,就是为了让Pod中的进程、服务能访问k8s集群而提出的一个概念,基于service account,pod中的进程、服务能获取到一个username和令牌Token,从而调用kubernetes集群的api server。### --- apiVersion: v1 kind: ServiceAccount metadata: name: nginx-ingress namespace: nginx-ingress RBAC 相关资源定义如下: ###RBAC:基于角色的访问控制机制,它只有允许授权,没有拒绝授权,因为默认是拒绝所有,我们仅需要定义允许该用户做什么即可。默认kubectl创建的K8s资源,都是默认启用强制RBAC的### --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nginx-ingress rules: - apiGroups: - "" resources: - services - endpoints verbs: - get - list - watch - apiGroups: - "" resources: - secrets verbs: - get - list - watch - apiGroups: - "" resources: - configmaps verbs: - get - list - watch - update - create - apiGroups: - "" resources: - pods verbs: - list - watch - apiGroups: - "" resources: - events verbs: - create - patch - list - apiGroups: - extensions resources: - ingresses verbs: - list - watch - get - apiGroups: - "extensions" resources: - ingresses/status verbs: - update - apiGroups: - k8s.nginx.org resources: - virtualservers - virtualserverroutes - globalconfigurations - transportservers - policies verbs: - list - watch - get - apiGroups: - k8s.nginx.org resources: - virtualservers/status - virtualserverroutes/status verbs: - update --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nginx-ingress subjects: - kind: ServiceAccount name: nginx-ingress namespace: nginx-ingress roleRef: kind: ClusterRole name: nginx-ingress apiGroup: rbac.authorization.k8s.io Secret 的定义如下: ###k8s secrets用于存储和管理一些敏感数据,比如密码,token,密钥等敏感信息。它把 Pod 想要访问的加密数据存放到 Etcd 中。然后用户就可以通过在 Pod 的容器里挂载 Volume 的方式或者环境变量的方式访问到这些 Secret 里保存的信息了。### --- apiVersion: v1 kind: Secret metadata: name: default-server-secret namespace: nginx-ingress type: Opaque data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN2akNDQWFZQ0NRREFPRjl0THNhWFhEQU5CZ2txaGtpRzl3MEJBUXNGQURBaE1SOHdIUVlEVlFRRERCWk8KUjBsT1dFbHVaM0psYzNORGIyNTBjbTlzYkdWeU1CNFhEVEU0TURreE1qRTRNRE16TlZvWERUSXpNRGt4TVRFNApNRE16TlZvd0lURWZNQjBHQTFVRUF3d1dUa2RKVGxoSmJtZHlaWE56UTI5dWRISnZiR3hsY2pDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUwvN2hIUEtFWGRMdjNyaUM3QlBrMTNpWkt5eTlyQ08KR2xZUXYyK2EzUDF0azIrS3YwVGF5aGRCbDRrcnNUcTZzZm8vWUk1Y2Vhbkw4WGM3U1pyQkVRYm9EN2REbWs1Qgo4eDZLS2xHWU5IWlg0Rm5UZ0VPaStlM2ptTFFxRlBSY1kzVnNPazFFeUZBL0JnWlJVbkNHZUtGeERSN0tQdGhyCmtqSXVuektURXUyaDU4Tlp0S21ScUJHdDEwcTNRYzhZT3ExM2FnbmovUWRjc0ZYYTJnMjB1K1lYZDdoZ3krZksKWk4vVUkxQUQ0YzZyM1lma1ZWUmVHd1lxQVp1WXN2V0RKbW1GNWRwdEMzN011cDBPRUxVTExSakZJOTZXNXIwSAo1TmdPc25NWFJNV1hYVlpiNWRxT3R0SmRtS3FhZ25TZ1JQQVpQN2MwQjFQU2FqYzZjNGZRVXpNQ0F3RUFBVEFOCkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWpLb2tRdGRPcEsrTzhibWVPc3lySmdJSXJycVFVY2ZOUitjb0hZVUoKdGhrYnhITFMzR3VBTWI5dm15VExPY2xxeC9aYzJPblEwMEJCLzlTb0swcitFZ1U2UlVrRWtWcitTTFA3NTdUWgozZWI4dmdPdEduMS9ienM3bzNBaS9kclkrcUI5Q2k1S3lPc3FHTG1US2xFaUtOYkcyR1ZyTWxjS0ZYQU80YTY3Cklnc1hzYktNbTQwV1U3cG9mcGltU1ZmaXFSdkV5YmN3N0NYODF6cFErUyt1eHRYK2VBZ3V0NHh3VlI5d2IyVXYKelhuZk9HbWhWNThDd1dIQnNKa0kxNXhaa2VUWXdSN0diaEFMSkZUUkk3dkhvQXprTWIzbjAxQjQyWjNrN3RXNQpJUDFmTlpIOFUvOWxiUHNoT21FRFZkdjF5ZytVRVJxbStGSis2R0oxeFJGcGZnPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdi91RWM4b1JkMHUvZXVJTHNFK1RYZUprckxMMnNJNGFWaEMvYjVyYy9XMlRiNHEvClJOcktGMEdYaVN1eE9ycXgrajlnamx4NXFjdnhkenRKbXNFUkJ1Z1B0ME9hVGtIekhvb3FVWmcwZGxmZ1dkT0EKUTZMNTdlT1l0Q29VOUZ4amRXdzZUVVRJVUQ4R0JsRlNjSVo0b1hFTkhzbysyR3VTTWk2Zk1wTVM3YUhudzFtMApxWkdvRWEzWFNyZEJ6eGc2clhkcUNlUDlCMXl3VmRyYURiUzc1aGQzdUdETDU4cGszOVFqVUFQaHpxdmRoK1JWClZGNGJCaW9CbTVpeTlZTW1hWVhsMm0wTGZzeTZuUTRRdFFzdEdNVWozcGJtdlFmazJBNnljeGRFeFpkZFZsdmwKMm82MjBsMllxcHFDZEtCRThCay90elFIVTlKcU56cHpoOUJUTXdJREFRQUJBb0lCQVFDZklHbXowOHhRVmorNwpLZnZJUXQwQ0YzR2MxNld6eDhVNml4MHg4Mm15d1kxUUNlL3BzWE9LZlRxT1h1SENyUlp5TnUvZ2IvUUQ4bUFOCmxOMjRZTWl0TWRJODg5TEZoTkp3QU5OODJDeTczckM5bzVvUDlkazAvYzRIbjAzSkVYNzZ5QjgzQm9rR1FvYksKMjhMNk0rdHUzUmFqNjd6Vmc2d2szaEhrU0pXSzBwV1YrSjdrUkRWYmhDYUZhNk5nMUZNRWxhTlozVDhhUUtyQgpDUDNDeEFTdjYxWTk5TEI4KzNXWVFIK3NYaTVGM01pYVNBZ1BkQUk3WEh1dXFET1lvMU5PL0JoSGt1aVg2QnRtCnorNTZud2pZMy8yUytSRmNBc3JMTnIwMDJZZi9oY0IraVlDNzVWYmcydVd6WTY3TWdOTGQ5VW9RU3BDRkYrVm4KM0cyUnhybnhBb0dCQU40U3M0ZVlPU2huMVpQQjdhTUZsY0k2RHR2S2ErTGZTTXFyY2pOZjJlSEpZNnhubmxKdgpGenpGL2RiVWVTbWxSekR0WkdlcXZXaHFISy9iTjIyeWJhOU1WMDlRQ0JFTk5jNmtWajJTVHpUWkJVbEx4QzYrCk93Z0wyZHhKendWelU0VC84ajdHalRUN05BZVpFS2FvRHFyRG5BYWkyaW5oZU1JVWZHRXFGKzJyQW9HQkFOMVAKK0tZL0lsS3RWRzRKSklQNzBjUis3RmpyeXJpY05iWCtQVzUvOXFHaWxnY2grZ3l4b25BWlBpd2NpeDN3QVpGdwpaZC96ZFB2aTBkWEppc1BSZjRMazg5b2pCUmpiRmRmc2l5UmJYbyt3TFU4NUhRU2NGMnN5aUFPaTVBRHdVU0FkCm45YWFweUNweEFkREtERHdObit3ZFhtaTZ0OHRpSFRkK3RoVDhkaVpBb0dCQUt6Wis1bG9OOTBtYlF4VVh5YUwKMjFSUm9tMGJjcndsTmVCaWNFSmlzaEhYa2xpSVVxZ3hSZklNM2hhUVRUcklKZENFaHFsV01aV0xPb2I2NTNyZgo3aFlMSXM1ZUtka3o0aFRVdnpldm9TMHVXcm9CV2xOVHlGanIrSWhKZnZUc0hpOGdsU3FkbXgySkJhZUFVWUNXCndNdlQ4NmNLclNyNkQrZG8wS05FZzFsL0FvR0FlMkFVdHVFbFNqLzBmRzgrV3hHc1RFV1JqclRNUzRSUjhRWXQKeXdjdFA4aDZxTGxKUTRCWGxQU05rMXZLTmtOUkxIb2pZT2pCQTViYjhibXNVU1BlV09NNENoaFJ4QnlHbmR2eAphYkJDRkFwY0IvbEg4d1R0alVZYlN5T294ZGt5OEp0ek90ajJhS0FiZHd6NlArWDZDODhjZmxYVFo5MWpYL3RMCjF3TmRKS2tDZ1lCbyt0UzB5TzJ2SWFmK2UwSkN5TGhzVDQ5cTN3Zis2QWVqWGx2WDJ1VnRYejN5QTZnbXo5aCsKcDNlK2JMRUxwb3B0WFhNdUFRR0xhUkcrYlNNcjR5dERYbE5ZSndUeThXczNKY3dlSTdqZVp2b0ZpbmNvVlVIMwphdmxoTUVCRGYxSjltSDB5cDBwWUNaS2ROdHNvZEZtQktzVEtQMjJhTmtsVVhCS3gyZzR6cFE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= 对 ConfigMap 的定义如下: ###ConfigMap是一种API对象,用来将非加密数据保存到键值对中。可以用作环境变量、命令行参数或者存储卷中的配置文件。### --- kind: ConfigMap apiVersion: v1 metadata: name: nginx-config namespace: nginx-ingress data: 对 Deployment 的定义如下: --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-ingress namespace: nginx-ingress spec: replicas: 1 selector: matchLabels: app: nginx-ingress template: metadata: labels: app: nginx-ingress spec: nodeSelector: role: ingress-nginx-controller serviceAccountName: nginx-ingress containers: - image: nginx/nginx-ingress:1.7.2 imagePullPolicy: IfNotPresent name: nginx-ingress ports: - name: http containerPort: 80 hostPort: 80 - name: https containerPort: 443 hostPort: 443 securityContext: allowPrivilegeEscalation: true runAsUser: 101 #nginx capabilities: drop: - ALL add: - NET_BIND_SERVICE env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name args: - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret 通过 kubectl create 命令创建 nginx-ingress-controller: $kubectl create -f nginx-ingress-daemonset.yaml 2. 创建 Ingress 策略 本例对域名 mywebsite.com 的访问设置 Ingress 策略,定义对其/demo 路径的访问转发到后端 webapp Service 的规则: # mywebsite-ingress.yaml --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mywebsite-ingress spec: rules: - host: mywebsite.com http: paths: - path: /demo pathType: ImplementationSpecific backend: service: name: webapp port: number: 8080 通过 kubectl create 命令创建 nginx-ingress-controller: $ kubectl create -f nginx-ingress-daemonset.yaml curl --resolve mywebsite.com:80:192.168.18.3 http://mywebsite.com/demo/ curl -H 'Host:mywebsite.com' http://192.168.18.3/demo/
(https://blog.csdn.net/weixin_42595012/article/details/100692084)
一、什么是Ingress?
Kubernetes
暴露服务的方式目前只有三种:LoadBlancer Service、NodePort Service、Ingress
;
1、Pod 漂移问题
在 Kubernetes 中,随着 Pod 的创建和销毁,Pod IP 肯定会动态变化;那么如何把这个动态的 Pod IP 暴露出去?可以借助于 Kubernetes 的 Service 机制,Service 可以以标签的形式选定一组带有指定标签的 Pod,并监控和自动负载他们的 Pod IP,那么我们向外只暴露 Service IP 就行了;
NodePort 模式:在每个节点上开起一个端口,然后转发到内部 Pod IP 上,访问方式:http://nodeip:nodeport,如下图所示:
2、端口管理问题
采用 NodePort 方式暴露服务面临的问题是,服务一旦多起来,NodePort 在每个节点上开启的端口会极其庞大,而且难以维护;
这时,我们能否使用一个Nginx直接对内进行转发呢?在集群内,Pod与Pod之间是可以互相通信的,而Pod是可以共享宿主机的网络名称空间的,也就是说当Pod共享宿主机的网络名称空间时,Pod上所监听的就是Node上的端口。那么这又该如何实现呢?简单的实现就是使用 DaemonSet 在每个 Node 上监听 80,然后写好规则,因为 Nginx 外面绑定了宿主机 80 端口(就像 NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示:
3、域名分配及动态更新问题
从上面的方法,采用 Nginx-Pod 似乎已经解决了问题,但是其实这里面有一个很大缺陷:当每次有新服务加入又该如何修改 Nginx 配置呢??我们知道使用Nginx可以通过虚拟主机域名进行区分不同的服务,而每个服务通过upstream进行定义不同的负载均衡池,再加上location进行负载均衡的反向代理,在日常使用中只需要修改 nginx.conf 即可实现,那在K8S中又该如何实现这种方式的调度呢???
假设后端的服务初始服务只有ecshop,后面增加了bbs和member服务,那么又该如何将这2个服务加入到Nginx-Pod进行调度呢?总不能每次手动改或者Rolling Update 前端 Nginx Pod 吧!!此时 Ingress 出现了。
Ingress 包含两大组件:Ingress Controller 和 Ingress。
Ingress 简单的理解就是你原来需要改 Nginx 配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yaml 创建,每次不要去改 Nginx 了,直接改 yaml 然后创建/更新就行了;
Ingress Controller 通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,并按照规则模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下,工作流程如下图:
五、部署Ingress控制器(Nginx)
1、部署Ingress controller
下载 官网 配置清单
[root@master ~]# mkdir ingress-nginx
[root@master ~]# cd ingress-nginx/
[root@master ingress-nginx]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
部署:
[root@master ingress-nginx]# kubectl apply -f mandatory.yaml
ingress-controller 使用的镜像是 quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.1,下载的时间较长,耐心等待即可,也可提前准备好镜像。
一个 ingress 资源对象的定义实例如下: --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mywebsite-ingress spec: rules: - host: mywebsite.com http: paths: - path: /demo pathType: ImplementationSpecific backend: service: name: webapp port: number: 8080 ingress 资源主要用于定义路由转发规则,可以包含多条转发规则的定义,通过 spec.rules 进行设置。下面对其中的关键配置进行说明。
为了实现灵活的负载分发策略,Ingress策略可以按多种方式进行配置,如下为几种常见的Ingress转发策略配置。 1.转发到单个后端服务上 基于这种设置,客户端发送到Ingress Controller的访问请求都将被转发到后端的唯一Service上,在这种情况下Ingress无须定义任何rule。只需设置一个默认的后端服务(defaultBackend) 通过如下所示的设置,对Ingress Controller的访问请求都将被转发到“myweb:8080”这个服务上。 # single backend service --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress spec: defaultBackend: service: name: webapp port: number: 8080 2.同一域名下,不同的URL路径被转发到不同的服务上 这种配置常用于一个网站通过不同的路径提供不同的服务的场景,例如/web表示访问Web页面,/api表示访问API接口,对应到后端的两个服务,只需要在 ingress 规则定义中设置将同一域名的不同 URL 路径转发到不同的后端服务,如图 4.9 所示。 通过如下所示的设置,对“mywebsite.com/web”的访问请求将被转发到“web-service:80”服务上;对“mywebsite.com/api”的访问请求将被转发到“api-service:80”服务上。 # ingress-simple-fanout.yaml --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: simple-fanout-example spec: rules: - host: mywebsite.com http: paths: - path: /web pathType: ImplementationSpecific backend: service: name: web-service port: number: 8080 - path: /api pathType: ImplementationSpecific backend: service: name: api-service port: number: 8081 3.不同的域名(虚拟主机名)被转发到不同的服务上 这种配置常用于一个网站通过不同的域名或虚拟主机名提供不同服务的场景,例如foo.bar.com域名由service1提供服务,bar.foo.com域名由service2提供服务,如图 4.10 所示。 通过如下所示的设置,对“foo.bar.com”的访问请求将被转发到“service1:80”服务上;对“bar.foo.com”的访问请求将被转发到“service2:80”服务上。 # route based on host --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: name-virtual-host-ingress spec: rules: - host: foo.bar.com http: paths: - pathType: Prefix path: "/" backend: service: name: service1 port: number: 80 - host: bar.foo.com http: paths: - pathType: Prefix path: "/" backend: service: name: service2 port: number: 80 4.不使用域名的转发规则 如果在 Ingress 中不定义任何 host 域名, Ingress Controller 则将所有客户端请求都转发到后端服务。例如下面的配置为将“<ingresscontroller-ip>/demo”的访问请求转发到“webapp:8080/demo”服务上: # not use host --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress spec: rules: - http: paths: - path: /demo pathType: Prefix backend: service: name: webapp port: number: 8080
K8S 支持为 Ingress 设置 TLS 安全访问机制,通过为 Ingress 的 host(域名)配置包含 TLS 私钥和证书的 Secret 进行支持。 Ingress资源仅支持单个TLS端口号443,并且假设在Ingress 访问点(Ingress Controller)结束TLS安全机制,向后端服务转发的流量将以明文形式发送。 如果 Ingress中的TLS配置部分指定了不同的host,那么它们将根据通过SNI TLS扩展指定的虚拟主机名(这要求Ingress Controller支持SNI)在同一端口进行复用。 TLS Secret中的文件名必须为“tls.crt”和“tls.key”,它们分别包含用于TLS的证书和私钥,例如: apiVersion:v1 kind: Secret metadata: name:testsecret-tls namespace:defaul tdata: tls.crt: base64 encoded cert t1s.key: base64 encoded key type:kubernetes.io/tls 然后,需要在Ingress 资源对象中引用该Secret,这将通知 Ingress Controller 使用TLS加密客户端到负载均衡器的网络通道。用户需要确保在TLS证书(tls.crt)中包含相应host的全限定域名(FQDN)被包含在其CN(Common Name)配置中。 TLS的功能特性依赖于Ingress Controller的具体实现,不同 Ingress Controller的实现机制可能不同,用户需要参考各个Ingress Controller的文档。 下面以Nginx Ingress为例,对Ingress的TLS配置进行说明,步骤如下。 (1)创建自签名的密钥和SSL证书文件。 (2)将证书保存到Kubernetes的Secret资源对象中。 (3)在Ingress 资源中引用该 Secret。 下面通过OpenSSL工具生成密钥和证书文件,将参数-subj中的/CN设置为host全限定域名(FQDN)“mywebsite.com": #opense1 req-x509-nodes-days5000-newkey rea: 2048-keyout t1s.key-out tle.crt-subj"/CN=mywebsite.com" 通过以上命令将生成 tls.key 和 tls.crt 两个文件。 然后根据 tls.key 和 tls.crt 文件创建 secret 资源对象,有以下两种方法。 方法一:使用 kubectl create secret tls 命令直接通过 tls.key和tls.crt文件创建 secret对象。 $kubectl create secret t1s mywebsite-ingress-secret--key tls.key --cert tls.crt 方法二:编辑 mywebsite-ingress-secret.yaml文件,将tls.key和tls.crt文件的内容经过BASE64编码的结果复制进去,使用 kubectl create 命令进行创建。 # mywebsite-ingress-secret.yaml --- apiVersion: v1 kind: Secret metadata: name: mywebsite-ingress-secret type: kubernetes.io/tls data: tls.crt: MIIDAzCCAeugAwIBAgIJALrTg9VLmFgdMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNVBAMMDW15d2Vic2l0ZS5jb20wHhcNMTcwNDIzMTMwMjA1WhcNMzAxMjMxMTMwMjA1WjAYMRYwFAYDVQQDDA1teXdlYnNpdGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApL1y1rq1I3EQ5E0PjzW8Lc3heW4WYTykPOisDT9Zgyc+TLPGj/YF4QnAuoIUAUNtXPlmINKuD9Fxzmh6q0oSBVb42BU0RzOTtvaCVOU+uoJ9MgJpd7Bao5higTZMyvj5a1M9iwb7k4xRAsuGCh/jDO8fj6tgJW4WfzawO5w1pDd2fFDxYn34Ma1pg0xFebVaiqBu9FL0JbiEimsV9y7V+g6jjfGffu2xl06X3svqAdfGhvS+uCTArAXiZgS279se1Xp834CG0MJeP7tamD44IfA2wkkmD+uCVjSEcNFsveY5cJevjf0PSE9g5wohSXphd1sIGyjEy2APeIJBP8bQ+wIDAQABo1AwTjAdBgNVHQ4EFgQUjmpxpmdFPKWkr+A2XLF7oqro2GkwHwYDVR0jBBgwFoAUjmpxpmdFPKWkr+A2XLF7oqro2GkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAVXPyfagP1AIov3kXRhI3WfyCOIN/sgNSqKM3FuykboSBN6c1w4UhrpF71Hd4nt0myeyX/o69o2Oc9a9dIS2FEGKvfxZQ4sa99iI3qjoMAuuf/Q9fDYIZ+k0YvY4pbcCqqOyICFBCMLlAct/aB0K1GBvC5k06vD4Rn2fOdVMkloW+Zf41cxVIRZe/tQGnZoEhtM6FQADrv1+jM5gjIKRX3s2/Jcxy5g2XLPqtSpzYA0F7FJyuFJXEG+P9X466xPi9ialUri66vkbUVT6uLXGhhunsu6bZ/qwsm2HzdPo4WRQ3z2VhgFzHEzHVVX+CEyZ8fJGoSi7njapHb08lRiztQ== tls.key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkvXLWurUjcRDkTQ+PNbwtzeF5bhZhPKQ86KwNP1mDJz5Ms8aP9gXhCcC6ghQBQ21c+WYg0q4P0XHOaHqrShIFVvjYFTRHM5O29oJU5T66gn0yAml3sFqjmGKBNkzK+PlrUz2LBvuTjFECy4YKH+MM7x+Pq2AlbhZ/NrA7nDWkN3Z8UPFiffgxrWmDTEV5tVqKoG70UvQluISKaxX3LtX6DqON8Z9+7bGXTpfey+oB18aG9L64JMCsBeJmBLbv2x7VenzfgIbQwl4/u1qYPjgh8DbCSSYP64JWNIRw0Wy95jlwl6+N/Q9IT2DnCiFJemF3WwgbKMTLYA94gkE/xtD7AgMBAAECggEAUftNePq1RgvwYgzPX29YVFsOiAV28bDh8sW/SWBrRU90O2uDtwSx7EmUNbyiA/bwJ8KdRlxR7uFGB3gLA876pNmhQLdcqspKClUmiuUCkIJ7lzWIEt4aXStqae8BzEiWpwhnqhYxgD3l2sQ50jQII9mkFTUtxbLBU1F95kxYjX2XmFTrrvwroDLZEHCPcbY9hNUFhZaCdBBYKADmWo9eV/xZJ97ZAFpbpWyONrFjNwMjjqCmxMx3HwOI/tLbhpvob6RT1UG1QUPlbB8aXR1FeSgt0NYhYwWKF7JSXcYBiyQubtd3T6RBtNjFk4b/zuEUhdFN1lKJLcsVDVQZgMsO4QKBgQDajXAq4hMKPH3CKdieAialj4rVAPyrAFYDMokW+7buZZAgZO1arRtqFWLTtp6hwHqwTySHFyiRsK2Ikfct1H16hRn6FXbiPrFDP8gpYveu31Cd1qqYUYI7xaodWUiLldrteun9sLr3YYR7kaXYRenWZFjZbbUkq3KJfoh+uArPwwKBgQDA95Y4xhcL0F5pE/TLEdj33WjRXMkXMCHXGl3fTnBImoRf7jF9e5fRK/v4YIHaMCOn+6drwMv9KHFL0nvxPbgbECW1F2OfzmNgm6l7jkpcsCQOVtuu1+4gK+B2geQYRA2LhBk+9MtGQFmwSPgwSg+VHUrm28qhzUmTCN1etdpeaQKBgGAFqHSO44Kp1S8Lp6q0kzpGeN7hEiIngaLh/y1j5pmTceFptocSa2sOfl86azPyF3WDMC9SU3a/Q18vkoRGSeMcu68O4y7AEK3VRiI4402nvAm9GTLXDPsp+3XtllwNuSSBznCxx1ONOuH3uf/tp7GUYR0WgHHeCfKy71GNluJ1AoGAKhHQXnBRdfHno2EGbX9mniNXRs3DyZpkxlCpRpYDRNDrKz7y6ziW0LOWK4BezwLPwz/KMGPIFVlL2gv5mY6rJLtQfTqsLZsBb36AZL+Q1sRQGBA3tNa+w6TNOwj2gZPUoCYcmu0jpB1DcHt4II8E9q18NviUJNJsx/GW0Z80DIECgYEAxzQBh/ckRvRaprN0v8w9GRq3wTYYD9y15U+3ecEIZrr1g9bLOi/rktXy3vqL6kj6CFlpwwRVLj8R3u1QPy3MpJNXYR1Bua+/FVn2xKwyYDuXaqs0vW3xLONVO7z44gAKmEQyDq2sir+vpayuY4psfXXK06uifz6ELfVyY6XZvRA= 如果需要配置 TLS 的 host 域名有多个,例如前面第 3 中 Ingress 策略配置方式,则 SSL 证书需要使用额外的一个 x509 v3 配置文件辅助完成,在[alt_names]段中完成多个 DNS 域名的设置。如下图:
1、桥接网络(Bridged Networking) 桥接网络是指本地物理网卡和虚拟网卡通过VMnet0虚拟交换机进行桥接,物理网卡和虚拟网卡在拓扑图上处于同等地位(虚拟网卡既不是Adepter VMnet1也不是Adepter VMnet8)。(那么物理网卡和虚拟网卡就相当于处于同一个网段) 2、NAT模式 在NAT网络中,会用到VMware Network AdepterVMnet8虚拟网卡,主机上的VMware Network AdepterVMnet8虚拟网卡被直接连接到VMnet8虚拟交换机上与虚拟网卡进行通信。 a. NAT网络模式 VMware NetworkAdepter VMnet8虚拟网卡的作用仅限于和VMnet8网段进行通信,它不给VMnet8网段提供路由功能,所以虚拟机虚拟一个NAT服务器,使虚拟网卡可以连接到Internet。在这种情况下,我们就可以使用端口映射功能,让访问主机80端口的请求映射到虚拟机的80端口上。 VMware Network Adepter VMnet8虚拟网卡的IP地址是在安装VMware时由系统指定生成的,我们不要修改这个数值,否则会使主机和虚拟机无法通信。 b. NAT虚拟网卡IP地址 物理网卡IP地址 虚拟出来的网段和NAT模式虚拟网卡的网段是一样的,都为192.168.111.X,包括NAT服务器的IP地址也是这个网段。在安装VMware之后同样会生成一个虚拟DHCP服务器,为NAT服务器分配IP地址。 当主机和虚拟机进行通信的时候就会调用VMwareNetwork Adepter VMnet8虚拟网卡,因为它们都在一个网段,所以通信就不成问题了。 实际上,VMware Network Adepter VMnet8虚拟网卡的作用就是为主机和虚拟机的通信提供一个接口,即使主机的物理网卡被关闭,虚拟机仍然可以连接到Internet,但是主机和虚拟机之间就不能互访了。
关于Kubernetes网络,我们通常有如下问趣Kubernetes的网络模型是什么?
Docker背后的网络基础是什么?
Docker自身的网络模型和局限是什么?
Kubernetes的网络组件之间是怎么通信的?
外部如何访问Kubernetes集群?
有哪些开源组件支持Kubernetes的网络模型?
本章分别回答这些问题,并通过一个具体的实验将这些相关的知识点串联成一个多体。
Kubernetes网络模型设计的一个基础原则是:每个Pod都拥有一个独立的IP地址,并假定所有Pod都在一个可以直接连通的、扁平的网络空间中。所以不管它们是否运行在同一个Node(宿主机)中,都要求它们可以直接通过对方的IP进行访问。设计这个原则的原因是,用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射到主机端口等问题。 实际上,在Kubernetes世界里,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作IP-per-Pod模型。 由于Kubernetes的网络模型假设Pod之间访问时使用的是对方Pod的实际地址,所以一个Pod内部的应用程序看到的自己的IP地址和端口与集群内其他Pod看到的一样。它们都是Pod实际分配的IP地址。将IP地址和端口在Pod内部和外部都保持一致,也就不需要使用NAT进行地址转换了。Kubernetes的网络之所以这么设计,主要原因就是可以兼容过去的应用。当然,我们使用 Linux 命令 ip addr show 也能看到这些地址,与程序看到的没有什么区别。所以这种IP-per-Pod的方案很好地利用了现有的各种域名解析和发现机制。 为每个Pod都设置一个IP地址的模型还有另外一层含义,那就是同一个Pod内的不同容器会共享同一个网络命名空间,也就是同一个Linux网络协议栈。这就意味着同一个POD内的容器可以通过 localhost 连接对方的端口。这种关系和同一个VM内的进程之间的关系是一样的,看起来Pod内容器之间的隔离性减小了,而且Pod内不同容器之间的端口是共享的,就没有所谓的私有端口的概念了。如果你的应用必须使用一些特定的端口范围,那也可以为这些应用单独创建一些Pod。反之,对那些没有特殊需要的应用,由于Pod内的容器是共享部分资源的,所以可以通过共享资源相互通信,这显然更加容易和高效。针对这些应用,虽然损失了可接受范围内的部分隔离性,却也是值得的。 IP-per-Pod模式和Docker原生的通过动态端口映射方式实现的多节点访问模式有什么区别呢?主要区别是后者的动态端口映射会引入端口管理的复杂性,而且访问者看到的IP地址和端口与服务提供者实际绑定的不同(因为NAT的缘故,它们都被映射成新的地址或端口),这也会引起应用配置的复杂化。同时,标准的DNS等名字解析服务也不适用了,甚至服务注册和发现机制都将迎来挑战,因为在端口映射情况下,服务自身很难知道自己对外暴露的真实服务IP和端口,外部应用也无法通过服务所在容器的私有IP地址和端口来访问服务。 总的来说,IP-per-Pod模型是一个简单的兼容性较好的模型。从该模型的网络的端口分配、域名解析、服务发现、负载均衡、应用配置和迁移等角度来看,Pod都能够被看作一台独立的虚拟机或物理机。 按照这个网络抽象原则,Kubernetes对网络有什么前提和要求呢? Kubernetes对集群网络有如下基本要求。 (1)所有Pod都可以在不用NAT的方式下同别的Pod通信。 (2)在所有节点上运行的代理程序(例如 kubelet或操作系统守护进程)都可以在不用NAT的方式下同所有Pod通信,反之亦然。 (3)在 hostnetwork 模式运行的 Pod 都可以在不用 NAT 的方式下同别的 Pod 通信。 这些基本要求意味着并不是两台机器都运行 Docker, K8S 就可以工作了。具体的集群网络实现必须满足上述要求,原生的 Docker 网络目前还不能很好地满足这些要求。 实际上,这些对网络模型的要求并没有降低整个网络系统的复杂度。如果你的程序原来在VM上运行,而那些VM拥有独立IP,并且它们之间可以直接透明地通信,那么Kubernetes的网络模型就和VM使用的网络模型一样。所以,使用这种模型可以很容易地的将已有的应用程序从VM或者物理机迁移到容器上。 当然,谷歌设计 Kubernetes的一个主要运行基础就是其公有云GCE,GCE默认支持这些网络要求。另外,常见的其他公有云服务商如亚马逊AWS、微软Azure等公有云环境也都支持这些网络要求。 由于部署私有云的场景非常普遍,所以在私有云中运行 Kubernetes+Docker 集群前,需要自己搭建符合Kubernetes要求的网络环境。有很多开源组件可以帮助我们打通跨主机容器之间的网络,实现满足Kubernetes要求的网络模型。当然,每种方案都有适合的场景,用户应根据自己的实际需要进行选择。在后续章节中会对常见的开源方案进行介绍。 Kubernetes的网络依赖于Docker,Docker的网络又离不开Linux操作系统内核特性的支持,所以我们有必要先深入了解 Docker 背后的网络原理和基础知识。接下来一起深入学习必要的Linux网络知识。
Docker容器互访三种方式:https://www.cnblogs.com/shenh/p/9714547.html
Docker技术依赖于近年来Linux内核虚拟化技术的发展,所以Docker对Linux 内核有很强的依赖。这里将Docker 使用到的与Linux网络有关的主要技术进行简单介绍,这些技术有网络命名空间(Network Namespace)、Veth 设备对、网桥、ipatables和路由。
#7.2.1 网络命名空间 为了支持网络协议栈的多个实例,Linux在网络栈中引入了网络命名空间,这些独立的协议栈被隔离到不同的命名空间中。处于不同命名空间中的网络栈是完全隔离的,彼此之间无法通信。通过对网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境。Docker正是利用了网络的命名空间特性,实现了不同容器之间的网络隔离。 在Linux的网络命名空间中可以有自己独立的路由表及独立的 iptables 设置来提供包转发、NAT及IP包过滤等功能。 为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建的套接字必须属于某个命名空间,套接字的操作也必须在命名空间中进行。同样,网络设备必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空间之间移动。当然,是否允许移动与设备的特征有关。让我们深入Linux操作系统内部,看看它是如何实现网络命名空间的,这也对理解后面的概念有帮助。 `1.网络命名空间的实现` Linux的网络协议栈是十分复杂的,为了支持独立的协议栈,相关的这些全局变量都必须被修改为协议栈私有。最好的办法就是让这些全局变量成为一个Net Namespace 变量的成员,然后为协议栈的函数调用加入一个Namespace参数。这就是Linux 实现网络命名空间的核心。 同时,为了保证对已经开发的应用程序及内核代码的兼容性,内核代码隐式地使用了命名空间中的变量。程序如果没有对命名空间有特殊需求,就不需要编写额外的代码,网络命名空间对应用程序而言是透明的。 在建立新的网络命名空间,并将某个进程关联到这个网络命名空间后,就出现了类似于如图 7.1 所示的内核数据结构,所有网站栈变量都被放入了网络命名空间的数据结构中。这个网络命名空间是其进程组私有的,和其他进程组不冲突。
在新生成的私有命名空间中只有回环设备(名为“lo”且是停止状态),其他设备默认都不存在,如果我们需要,则要一一手工建立。Docker容器中的各类网络栈设备都是Docker Daemon在启动时自动创建和配置的。所有网络设备(物理的或虚拟接口、桥等在内核里都叫作Net Device)都只能属于一个命名空间。当然,物理设备(连接实际硬件的设备)通常只能关联到root这个命名空间中。虚拟网络设备(虚拟以太网接口或者虚拟网口对)则可以被创建并关联到一个给定的命名空间中,而且可以在这些命名空间之间移动。 前面提到,由于网络命名空间代表的是一个独立的协议栈,所以它们之间是相互隔离的,彼此无法通信,在协议栈内部都看不到对方。那么有没有办法打破这种限制,让处于不同命名空间中的网络相互通信,甚至与外部的网络进行通信呢?答案是“有,应用Veth设备对即可”。Veth 设备对的一个重要作用就是打通了相互看不到的协议栈之间的壁垒,它就像一条管子,一端连着这个网络命名空间的协议栈,一端连着另一个网络命名空间的协议栈。所以如果想在两个命名空间之间通信,就必须有一个Veth设备对。后面会介绍如何操作Veth设备对来打通不同命名空间之间的网络。 `2.对网络命名空间的操作` 下面列举对网络命名空间的一些操作。我们可以使用Linux iproute2系列配置工具中的IP命令来操作网络命名空间。注意,这个命令需要由root用户运行。 创建一个命名空间: ip netns add <name> 在命名空间中运行命令: ip netns exec <name> <command> 也可以先通过bash命令进入内部的Shell界面,然后运行各种命令: ip netns exec <name> bash 退出到外面的命名空间时,请输入“exit”。 `3.网络命名空间操作中的实用技巧` 操作网络命名空间时的一些实用技巧如下。 我们可以在不同的网络命名空间之间转移设备,例如下面会提到的Veth设备对的转移。因为一个设备只能属于一个命名空间,所以转移后在这个命名空间中就看不到这个设备了。具体哪些设备能被转移到不同的命名空间中呢?在设备里面有一个重要的属性:NETIF_F_ETNS_LOCAL,如果这个属性为on,就不能被转移到其他命名空间中了。Veth设备属于可以转移的设备,而很多其他设备如lo设备、vxlan 设备、ppp 设备、bridge 设备等都是不可以转移的。将无法转移的设备移动到别的命名空间时,会得到参数无效的错误提示: # ip link set br0 netns ns1 RTNETLINK answers: Invalid argument 如何知道这些设备是否可以转移呢?可以使用ethtool工具查看: # ethtool -k br0 netns-local: on [fixed] netns-local的值是on,说明不可以转移,否则可以转移。
#7.2.2 Veth 设备对
引入Veth 设备对是为了在不同的网络命名空间之间通信,利用它可以直接将两个网络命名空间连接起来。由于要连接两个网络命名空间,所以Veth设备都是成对出现的,很像一对以太网卡,并且中间有一根直连的网线。既然是一对网卡,那么我们将其中一端称为另一端的 peer。在Veth设备的一端发送数据时,它会将数据直接发送到另一端,并触发另一端的接收操作。
整个Veth的实现非常简单,有兴趣的读者可以参考源代码 “drivers/net/veth.c” 中的实现。如图 7.2 所示是Veth设备对示意图。
1.对Veth 设备对的操作命令接下来看看如何创建 Veth 设备对,如何将其连接到不同的命名空间中,并设置其地址,让它们通信。
创建 Veth 设备对:
#7.2.3 网桥
Linux可以支持多个不同的网络,它们之间能够相互通信,如何将这些网络连接起来并实现各网络中主机的相互通信呢?可以用网桥。网桥是一个二层的虚拟网络设备,把若干个网络接口“连接”起来,以使得网络接口之间的报文能够相互转发。网桥能够解析收发的报文,读取目标MAC地址的信息,将其与自己记录的MAC表结合,来决策报文的转发目标网络接口。为了实现这些功能,网桥会学习源MAC地址(二层网桥转发的依据就是MAC地址)。在转发报文时,网桥只需向特定的网口进行转发,来避免不必要的网络交互。如果它遇到一个自己从未学习到的地址,就无法知道这个报文应该向哪个网络接口转发,将报文广播给所有的网络接口(报文来源的网络接口除外)。
在实际的网络中,网络拓扑不可能永久不变。设备如果被移动到另一个端口上,却没有发送任何数据,网桥设备就无法感知这个变化,网桥还是向原来的端口转发数据包,在这种情况下数据会丢失。所以网桥还要对学习到的MAC 地址表加上超时时间(默认为5min)。如果网桥收到了对应端口MAC地址回发的包,则重置超时时间,否则过了超时时间,就认为设备已经不在那个端口上了,它会重新广播发送。
在Linux的内部网络栈里实现的网桥设备,作用和上面的描述相同。Linux 主机过去一般只有一个网卡,现在多网卡的机器越来越多,而且有很多虚拟设备存在,所以Linux 网桥提供了在这些设备之间相互转发数据的二层设备。Linux 内核支持网口的桥接(目前只支持以太网接口)。但是与单纯的交换机不同,交换机只是一个二层设备,对于接收到的报文,要么转发,要么丢弃。运行着Linux内核的机器本身就是一台主机,有可能是网络报文的目的地,其收到的报文除了转发和丢弃,还可能被送到网络协议栈的上层(网络层),从而被自己(这台主机本身的协议栈)消化,所以我们既可以把网桥看作一个二层设备,也可以把它看作一个三层设备。
1.Linux 网桥的实现Linux 内核是通过一个虚拟网桥设备(Net Device)来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起来。如图7.3所示,这种Net Device 网桥和普通的设备不同,最明显的一个特性是它还可以有一个IP地址。
如图7.3所示,网桥设备br0绑定了eth0和ethl。对于网络协议栈的上层来说,只看得到br0就行。因为桥接是在数据链路层实现的,上层不需要关心桥接的细节,所以协议栈上层需要发送的报文被送到br0,网桥设备的处理代码判断报文应该被转发到eth0还是ethl,或者两者应该皆转发;反过来,从eth0或从ethl接收到的报文被提交给网桥的处理代码,在这里会判断报文应该被转发、丢弃还是被提交到协议栈上层。而有时eth0、ethl也可能会作为报文的源地址或目的地址,直接参与报文的发送与接收,从而绕过网桥。
`自己总结:Dcoker 同 node 间容器通信可以直接通信,靠的是 Docker0 (br0) 网桥;跨 node 容器通信需要 Open vSwitch、weave等实现跨主机容器连接`
标准的 Docker 支持以下4类网络模式。
· host模式:使用 --net=host 指定。
· container模式:使用 --net=container:NAME_or_ID 指定。
· none模式:使用 --net=none 指定。
· bridge模式:使用 --net=bridge 指定,为默认设置。
在Kubernetes管理模式下通常只会使用bridge模式,所以本节重点介绍在bridge模式下Docker是如何支持网络的。
在bridge模式下,Docker Daemon首次启动时会创建一个虚拟网桥,默认的名称是docker0,然后按照RPC1918的模型在私有网络空间中给这个网桥分配一个子网。针对由Docker创建的每一个容器,都会创建一个虚拟以太网设备(Veth设备对),其中一端关联到网桥上,另一端使用Linux的网络命名空间技术映射到容器内的eth0设备,然后在网桥的地址段内给eth0接口分配一个IP地址。如图 7.6 所示就是 Docker 的默认桥接网络模型。
其中 ip1 是网桥的IP地址,Docker Daemon会在几个备选地址段里给它选一个地址,通常是以172开头的一个地址,这个地址和主机的IP地址是不重叠的。ip2是Docker在启动容器时在这个地址段选择的一个没有使用的IP地址,它被分配给容器,相应的MAC地址也根据这个IP地址,在02:42:ac:11:00:00和02:42:ac:11:ff:ff的范围内生成,这样做可以确保不会有ARP冲突。
启动后,Docker 还将 Veth 设备对的名称映射到 eth0 网络接口。ip3 就是主机的网卡地址。在一般情况下,ip1、ip2和ip3是不同的IP段,所以在默认不做任何特殊配置的情况下,在外部是看不到 ip1 和 ip2 的。
这样做的结果就是,在同一台机器内的容器之间可以相互通信,不同主机上的容器不能相互通信,实际上它们甚至有可能在相同的网络地址范围内(不同主机上的docker0的地址段可能是一样的)。为了让它们跨节点相互通信,就必须在主机的地址上分配端口,然后通过这个端口将网络流量路由或代理到目标容器上。这样做显然意味着一定要在容器之间小心谨慎地协调好端口的分配情况,或者使用动态端口的分配技术。在不同应用之间协调好端口分配情况是十分困难的事情,特别是集群水平扩展时。而动态端口分配也会大大增加复杂度,例如:每个应用程序都只能将端口看作一个符号(因为是动态分配的,所以无法提前设置)。而且API Server 要在分配完后,将动态端口插入配置的合适位置,服务也必须能相互找到对方等。这些都是Docker的网络模型在跨主机访问时面临的问题。
在实际的业务场景中,业务组件之间的关系十分复杂,特别是随着微服务理念逐步深入人心,应用部署的粒度更加细小和灵活。为了支持业务应用组件的通信,Kubernetes网络的设计主要致力于解决以下问题。
(1)容器到容器之间的直接通信。
(2)抽象的Pod到Pod之间的通信。
(3)Pod到Service之间的通信。
(4)集群内部与外部组件之间的通信。
其中第3条、第4条在之前的章节里都有所讲解,本节对更为基础的第1条与第2条进行深入分析和讲解。
#容器到容器的通信
同一个Pod内的容器(Pod内的容器是不会跨宿主机的)共享同一个网络命名空间,共享同一个Linux协议栈。所以对于网络的各类操作,就和它们在同一台机器上一样,它们甚至可以用localhost地址访问彼此的端口。
这么做的结果是简单、安全和高效,也能减少将已存在的程序从物理机或者虚拟机中移植到容器下运行的难度。其实,在容器技术出来之前,大家早就积累了如何在一台机器上运行一组应用程序的经验,例如,如何让端口不冲突,以及如何让客户端发现它们等。
我们来看一下 Kubernetes 是如何利用Docker的网络模型的。如图7.8中的阴影部分所示,在Node上运行着一个Pod实例。在我们的例子中,容器就是图7.8中的容器1和容器2。容器1和容器2共享一个网络的命名空间,共享一个命名空间的结果就是它们好像在一台机器上运行,它们打开的端口不会有冲突,可以直接使用Linux的本地IPC进行通信(例如消息队列或者管道)。其实,这和传统的一组普通程序运行的环境是完全一样的,传统程序不需要针对网络做特别的修改就可以移植,它们之间的相互访问只需使用localhost就可以。例如,如果容器2运行的是MySQL,那么容器1使用localhost:3306就能直接访问这个运行在容器2上的MySQL了。
# Pod 之间的通信
我们看了同一个Pod内容器之间的通信情况,再看看Pod之间的通信情况。
每一个Pod都有一个真实的全局IP地址,同一个Node内的不同Pod之间可以直接采用对方Pod的IP地址通信,而且不需要采用其他发现机制,例如DNS、Consul(用于实现分布式系统的服务发现、服务隔离、服务配置)或者 etcd。
Pod间既有可能在同一个Node上运行,也有可能在不同的Node上运行,所以通信也分为两类:同一个Node上Pod之间的通信和不同Node上Pod之间的通信。
`1.同一个Node上Pod之间的通信`
同一个Node上两个Pod之间的关系如图 7.9 所示。
可以看出,Pod1和Pod2都是通过Veth连接到同一个 docker0网桥的,它们的IP地址IP1、IP2都是从docker0的网段上动态获取的,和网桥本身的IP3属于同一个网段。
另外,在Pod1、Pod2的Linux协议栈上,默认路由都是docker0的地址,也就是说所有非本地地址的网络数据,都会被默认发送到docker0网桥上,由docker0网桥直接中转。
综上所述,由于它们都关联在同一个docker0网桥上,地址段相同,所以它们之间是能直接通信的。
`2.不同Node上Pod之间的通信` Pod的地址是与docker0在同一个网段的,我们知道 docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行,因此要想实现不同Node上Pod容器之间的通信,就必须想办法通过主机的这个IP地址进行寻址和通信。 另一方面,这些动态分配且藏在 docker0后的“私有”IP地址也是可以找到的。Kubernetes会记录所有正在运行的Pod的IP分配信息,并将这些信息保存在etcd中(作为Service的Endpoint)。这些私有IP信息对于Pod到Pod的通信也是十分重要的,因为我们的网络模型要求Pod到Pod使用私有IP进行通信。所以首先要知道这些IP是什么。 之前提到,Kubernetes的网络对Pod的地址是平面的和直达的,所以这些Pod的IP规划也很重要,不能有冲突。只要没有冲突,我们就可以想办法在整个Kubernetes的集群中找到它。 综上所述,要想支持不同Node上Pod之间的通信,就要满足两个条件: (1)在整个Kubernetes集群中对Pod的IP分配进行规划,不能有冲突; (2)找到一种办法,将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以相互访问。 根据条件1的要求,我们需要在部署 Kubernetes时对 docker0的IP地址进行规划,保证每个Node上的docker0地址都没有冲突。我们可以在规划后手工配置到每个Node上,或者做一个分配规则,由安装的程序自己去分配占用。例如,Kubernetes的网络增强开源软件Flannel就能够管理资源池的分配。 根据条件2的要求,Pod中的数据在发出时,需要有一个机制能够知道对方Pod的IP地址挂在哪个具体的Node上。也就是说,先要找到Node对应宿主机的IP地址,将数据发送到这个宿主机的网卡,然后在宿主机上将相应的数据转发到具体的docker0上。一旦数据到达宿主机Node,那个Node内部的 docker0便知道如何将数据发送到Pod了。 如图7.10所示,IP1对应的是Pod1,IP2对应的是Pod2,Podl在访问Pod2时,首先要将数据从源Node的 eth0 发送出去,找到并到达Node2的 eth0 ,即先是从IP3到IP4的传送,之后才是从IP4到IP2的传送。 在谷歌的GCE环境中,Pod的IP管理(类似docker0)、分配及它们之间的路由打通都是由GCE完成的。Kubernetes作为主要在GCE上面运行的框架,它的设计是假设底层已经具备这些条件,所以它分配完地址并将地址记录下来就完成了自己的工作。在实际的GCE环境中,GCE的网络组件会读取这些信息,实现具体的网络打通工作。而在实际生产环境中,因为安全、费用、合规等种种原因,Kubernetes的客户不可能全部使用谷歌的GCE环境,所以在实际的私有云环境中,除了需要部署 Kubernetes和Docker,还需要额外的网络配置,甚至通过一些软件来实现Kubernetes对网络的要求。做到这些后,Pod和Pod之间才能无差别地进行透明通信。 为了达到这个目的,开源界有不少应用增强了Kubernetes、Docker的网络,在后续章节中会介绍几个常用的组件及其组网原理。
随着容器技术在企业生产系统中的逐步落地,用户对容器云的网络特性要求也越来越高。跨主机容器间的网络互通已经成为基本要求,更高的要求包括容器固定IP地址、一个容器多个IP地址、多个子网隔离、ACL控制策略、与SDN集成等。目前主流的容器网络模型主要有Docker公司提出的Container Network Model(CNM)模型和CoreOS公司提出的Container Network Interface(CNI)模型。
#CNM 网络模型简介
CNM模型是由Docker公司提出的容器网络模型,现在已经被Cisco Contiv、Kuryr、Open Virtual Networking(OVN)、Project Calico、 VMware、Weave和Plumgrid等项目采纳。另外,Weave、Project Calico、Kuryr和Plumgrid等项目也为CNM提供了网络插件的具体实现。
CNM模型主要通过Network Sandbox、Endpoint和Network这3个组件进行实现。
◎ Network Sandbox:容器内部的网络栈,包括网络接口、路由表、DNS等配置的管理。Sandbox可用Linux网络命名空间、FreeBSD Jail等机制进行实现。一个Sandbox可以包含多个Endpoint。
◎ Endpoint:用于将容器内的Sandbox与外部网络相连的网络接口。可以使用veth对、Open vSwitch的内部port等技术进行实现。一个Endpoint仅能够加入一个Network。
◎ Network:可以直接互连的Endpoint的集合。可以通过Linux网桥、VLAN等技术进行实现。一个Network包含多个Endpoint。
#CNI 网络模型
CNI是由CoreOs公司公司提出的另一种容器网络规范,现在已经被`Kubernetes`、rkt、Apache Mesos、Cloud Foundry和Kurma等项目采纳。另外,Contiv Networking, Project Calico、Weave、SR-IOV、Cilium、Infoblox、Multus、Romana、Plumgrid和Midokura等项目也为CNI提供网络插件的具体实现。下图描述了容器运行环境与各种网络插件通过CNI进行连接的模型。
CNI定义的是容器运行环境与网络插件之间的简单接口规范,通过一个JSON Schema定义CNI插件提供的输入和输出参数。一个容器可以通过绑定多个网络插件加入多个网络中。
# Flannel 插件的原理和部署示例
# Open vSwitch 插件的原理和部署示例
# 直接路由的原理和部署示例
# Calico 插件的原理和部署示例
容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性。如果多个容器希望共享同一份存储,则仅仅依赖容器本身是很难实现的。在Kubernetes系统中,将对容器应用所需的存储资源抽象为存储卷(Volume)概念来解决这些问题。 Volume 是与Pod绑定的(独立于容器)与Pod具有相同生命周期的资源对象。我们可以将 Volume 的内容理解为目录或文件,容器如需使用某个Volume,则仅需设置VolumeMounts 将一个或多个Volume挂载为容器中的目录或文件,即可访问 Volume中的数据。Volume具体是什么类型,以及由哪个系统提供,对容器应用来说是透明的。 Kubernetes 目前支持的 Volume类型包括 Kubernetes的内部资源对象类型、开源共享存储类型、存储厂商提供的硬件存储设备和公有云提供的存储等。 存储机制概述将Kubernetes特定类型的资源对象映射为目录或文件,包括以下类型的资源对象。 · ConfigMap:应用配置。 · Secret:加密数据。 · DownwardAPI:Pod或Container的元数据信息。 · ServiceAccountToken: Service Account中的 token 数据。 · Projected Volume:一种特殊的存储卷类型,用于将一个或多个上述资源对象一次性挂载到容器内的同一个目录下。 Kubernetes管理的宿主机本地存储类型如下。 · EmptyDir:临时存储。 · HostPath:宿主机目录。 持久化存储(PV)和网络共享存储类型如下。 · CephFS:一种开源共享存储系统。 · Cinder:一种开源共享存储系统。 · CSI:容器存储接口(由存储提供商提供驱动程序和存储管理程序)。 · FC(Fibre Channel):光纤存储设备。 · FlexVolume:一种基于插件式驱动的存储。 · Flocker:一种开源共享存储系统。 · Glusterfs:一种开源共享存储系统。 · iSCSI:iSCSI存储设备。 · Local:本地持久化存储。 · NFS:网络文件系统。 · PersistentVolumeClaim:简称PVC,持久化存储的申请空间。 · Portworx Volumes:Portworx提供的存储服务。 · Quobyte Volumes:Quobyte提供的存储服务。 · RBD(Ceph Block Device):Ceph块存储。 存储厂商提供的存储卷类型如下。 · ScaleIO Volumes:DellEMC的存储设备。 · StorageOS:StorageOS提供的存储服务。 · VsphereVolume:VMWare提供的存储系统。 公有云提供的存储卷类型如下。 · AWSElasticBlockStore:AWS公有云提供的Elastic Block Store。 · AzureDisk:Azure公有云提供的Disk。 · AzureFile:Azure公有云提供的File。 · GCEPersistentDisk:GCE公有云提供的Persistent Disk。
在Kubernetes中有一些资源对象可以以存储卷的形式挂载为容器内的目录或文件,目前包括 ConfigMap、Secret、Downward API、ServiceAccountToken、Projected Volume。下面对这几种类型如何以存储卷的形式使用进行说明。 # 1.ConfigMap ConfigMap 主要保存应用程序所需的配置文件,并且通过 Volume形式挂载到容器内的文件系统中,供容器内的应用程序读取。 例如,一个包含两个配置文件的ConfigMap 资源如下: --- apiVersion: v1 kind: ConfigMap metadata: name: cm-appconfigfiles data: key-serverxml: | <?xml version='1.0' encoding='utf-8'?> ...... key-loggingproperties: "handlers ...... = 4host-manager.org.apache.juli.FileHandler\r\n\r\n" 在 Pod 的 YAML 配置中,可以将 ConfigMap 设置为一个 Volume,然后在容器中通过 VolumeMounts 将 ConfigMap 类型的 Volume 挂载到 /configfiles 目录下: --- apiVersion: v1 kind: Pod metadata: name: cm-test-app spec: containers: - name: cm-test-app image: kubeguide/tomcat-app:v1 ports: - containerPort: 8080 volumeMounts: - name: serverxml # 引用Volume的名称 mountPath: /configfiles # 挂载到容器内的目录下 volumes: - name: serverxml # 定义Volume的名称 configMap: name: cm-appconfigfiles # 使用ConfigMap"cm-appconfigfiles" items: - key: key-serverxml # key=key-serverxml path: server.xml # 挂载为server.xml 文件 - key: key-loggingproperties # key=key-1oggingproperties path: logging.properties # 挂载为logging.properties 文件 在Pod成功创建之后,进入容器内部查看在 /configfiles 目录下存在 server.xml 和 logging.properties 文件。 ConfigMap中的配置内容如果是UTF-8编码的字符,则将被系统认为是文本文件,如果是其它字符,则系统将以二进制数据格式进行保存(设置binaryData字段) 关于 Pod 如何使用 configMap 的详细说明请参见 3.5 节的说明。 # 2.Secret 假设在 K8S 中已经存在如下 Secret 资源: --- apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: password: dmFsdWUtMg0K username: dmFsdWUtMQ0K 与 ConfigMap 的用法类似,在 Pod 的 YAML 配置中可以将 Secret 设置为一个 Volume,然后在容器内通过 volumeMounts 将 Secret 类型的 volume 挂载到 /etc/foo 目录下: --- apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: mycontainer image: redis volumeMounts: - name: foo mountPath: "/etc/foo" volumes: - name: foo secret: secretName: mysecret 关于 Secret 的详细说明请参见 6.5 节。 # 3.Downward API (在容器内获得 Pod 信息) 通过 Downward API 可以将 Pod 或 Container 的某些元数据信息(例如 Pod 名称、Pod IP、Node IP、Label、Annotation、容器资源限制等)以文件形式挂载到容器内,供容器内的应用使用。下面是一个将 Pod 标签通过 Downward API 挂在为容器内文件的示例: --- apiVersion: v1 kind: Pod metadata: name: kubernetes-downwardapi-volume-example labels: zone: us-est-coast cluster: test-cluster1 rack: rack-22 annotations: build: two builder: john-doe spec: containers: - name: client-container image: busybox command: ["sh", "-c"] args: - while true; do if [[ -e /etc/podinfo/labels ]]; then echo -en '\n\n'; cat /etc/podinfo/labels; fi; if [[ -e /etc/podinfo/annotations ]]; then echo -en '\n\n'; cat /etc/podinfo/annotations; fi; sleep 5; done; volumeMounts: - name: podinfo mountPath: /etc/podinfo volumes: - name: podinfo downwardAPI: items: - path: "labels" fieldRef: fieldPath: metadata.labels - path: "annotations" fieldRef: fieldPath: metadata.annotations # 4.Projected Volume 和 Service Account Token Projected Volume 是一种特殊的存储卷类型,用于将一个或多个上述资源对象(ConfigMap、Secret、Downward API)一次性挂载到容器内的同一个目录下。 从上面的几个示例来看,如果Pod希望同时挂载 ConfigMap、Secret、Downward API,则需要设置多个不同类型的Volume,再将每个Volume都挂载为容器内的目录或文件。如果应用程序希望将配置文件和密钥文件放在容器内的同一个目录下,则通过多个Volume就无法实现了。为了支持这种需求,Kubernetes引入了一种新的 Projected Volume 存储卷类型,用于将多种配置类数据通过单个Volume挂载到容器内的单个目录下。 Projected Volume的一些常见应用场景如下。 · 通过Pod的标签生成不同的配置文件,需要使用配置文件,以及用户名和密码,这时需要使用3种资源:ConfigMap、Secrets、Downward API。 · 在自动化运维应用中使用配置文件和账号信息时,需要使用ConfigMap、Secrets。 · 在配置文件内使用Pod名称(metadata.name)记录日志时,需要使用ConfigMap、Downward API。 · 使用某个Secret对Pod所在命名空间(metadata.namespace)进行加密时,需要使用Secret、Downward API。 Projected Volume 在Pod的Volume定义中类型为projected,通过 sources 字段设置一个或多个 ConfigMap、Secret、DownwardAPI、ServiceAccountToken 资源。各种类型的资源的配置内容与被单独设置为Volume时基本一样,但有两个不同点。 · 对于Secret类型的Volume,字段名“secretName”在projected.sources.secret中被改为“name”。 · Volume的挂载模式“defaultMode”仅可以设置在projected级别,对于各子项,仍然可以设置各自的挂载模式,使用的字段名为“mode”。 此外,Kubernetes从1.11版本开始引入对 ServiceAccountToken的挂载支持,在1.2 版本时达到Beta 阶段。ServiceAccountToken 通常用于容器内应用访问API Server鉴权的场景中。 下面是一个使用Projected Volume 挂载 ConfigMap、Secret、Downward API共3种资源的示例: --- apiVersion: v1 kind: Pod metadata: name: volume-test spec: containers: - name: container-test image: busybox volumeMounts: - name: all-in-one mountPath: "/projected-volume" readOnly: true volumes: - name: all-in-one projected: sources: - secret: name: mysecret items: - key: username path: my-group/my-username - downwardAPI: items: - path: "labels" fieldRef: fieldPath: metadata.labels - path: "cpu_limit" resourceFieldRef: containerName: container-test resource: limits.cpu - configMap: name: myconfigmap items: - key: config path: my-group/my-config 下面是使用 Projected Volume 挂载两个 secret 资源,其中一个设置了非默认挂载模式(mode)的示例: --- apiVersion: v1 kind: Pod metadata: name: volume-test spec: containers: - name: container-test image: busybox volumeMounts: - name: all-in-one mountPath: "/projected-volume" readOnly: true volumes: - name: all-in-one projected: sources: - secret: name: mysecret items: - key: username path: my-group/my-username - secret: name: mysecret2 items: - key: password path: my-group/my-password mode: 511 下面是一个使用 Projected Volume 挂载 ServiceAccountToken 的示例: --- apiVersion: v1 kind: Pod metadata: name: sa-token-test spec: containers: - name: container-test image: busybox volumeMounts: - name: token-vol mountPath: "/service-account" readOnly: true volumes: - name: token-vol projected: sources: - serviceAccountToken: audience: api expirationSeconds: 3600 path: token 对于 ServiceAccountToken 类型的Volume,可以设置 Token 的 audience、eapirationSeconds、path等属性信息。 · Oaudience:预期受众的名称。Token的接收者必须使用其中的audience标识符来标识自己,否则应该拒绝该Token。该字段是可选的,默认为API Server的标识符“api”。 · OexpirationSeconds:Service Account Token的过期时间,默认为1h,至少为10min(600s)。管理员可以通过 kube-apiserver 的启动参数 --service-account-max-token-expiration 限制Token的最长有效时间。 · path:挂载目录下的相对路径。 关于Service Account概念和应用的详细说明请参见6.4节。
Kubernetes管理的Node本地存储卷(Voiume)的类型如下。 · EmptyDir:与Pod同生命周期的Node临时存储。 · HostPath:Node目录。 · Local:基于持久卷(PV)管理的Node目录,详见下节的说明。 下面对这几种类型如何以存储卷的形式使用进行说明。 # 1. EmptyDir 这种类型的 Volume 将在Pod被调度到Node时进行创建,在初始状态下目录中是空的,所以命名为“空目录”(Empty Directory),它与Pod具有相同的生命周期,当Pod被毁时,Nade上相应的目录也会被删除。同一个Pod中的多个容器都可以挂载这种Volume。 由于EmptyDir类型的存储卷的临时性特点,它通常可以用于以下应用场景中。 · 基于磁盘进行合并排序操作时需要的暂存空间。 · 长时间计算任务的中间检查点文件。 · 为某个Web服务提供的临时网站内容文件。 在默认情况下,kubelet会在Node的工作目录下为Pod创建 EmptyDir目录,这个目录的存储介质可能是本地磁盘、SSD磁盘或者网络存储设备,取决于环境的配置。 另外,EmptyDir可以通过medium字段设置存储介质为“Memory”,表示使用基于内存的文件系统(tmpfs、RAM-backed filesystem).虽然tmpfs的读写速度非常快,但与磁盘中的目录不同,在主机重启之后,tmpfs的内容就会被清空。此外,写入 tmpfs的数据将被统计为容器的内存使用量,受到容器级别内存资源上限(Memory Resource Limit)的限制。 下面是使用EmptyDir类型的存储卷的Pod的YAML配置示例,该类型的存储卷的参数只有一对花括号“{}”: --- apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - image: busybox name: test-container volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: {} # 2.HostPath HosiPath 类型的存储卷用于将 Node 文件系统的目录或文件挂载到容器内部使用。对于大多数容器应用来说,都不需要使用宿主机的文件系统。适合使用HostPath存储卷的一些应用场景如下。 · 容器应用的关键数据需要被持久化到宿主机上。 · 需要使用Docker中的某些内部数据,可以将主机的/var/lib/docker 目录挂载到容器内。 · 监控系统,例如cAdvisor需要采集宿主机/sys目录下的内容。 · Pod的启动依赖于宿主机上的某个目录或文件就绪的场景。 HostPath存储卷的主要配置参数为path,设置为宿主机的目录或文件路径;还可以设置一个可选的参数type,表示宿主机路径的类型。目前支持的type配置参数和校验规则如表8.1所示。 由于HostPath使用的是宿主机的文件系统,所以在使用时有以下注意事项。 · 对于具有相同 HostPath 设置的多个Pod(例如通过podTemplate创建的)来说,可能会被Master 调度到多个Node上运行,但如果多个Node上HostPath中的文件内容(例如是配置文件)不同,则各Pod应用的运行可能出现不同的结果。 · 如果管理员设置了基于存储资源情况的调度策略,则HostPath目录下的磁盘空间将无法计入Node的可用资源范围内,可能出现与预期不同的调度结果。 · 如果是之前不存在的路径,则由kubelet自动创建出来的目录或文件的owner将是root,这意味着如果容器内的运行用户(User)不是root,则将无法对该目录进行写操作,除非将容器设置为特权模式(Privileged),或者由管理员修改 HostPath的权限以使得非root用户可写。 · HostPath 设置的宿主机目录或文件不会随着Pod的销毁而删除,在Pod不再存在之后,需要由管理员手工删除。 下面是使用HostPath类型的存储卷的Pod的YAML配置示例,其中将宿主机的 /data 目录挂载为容器内的 /host-data 目录: --- apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - image: busybox name: test-container volumeMounts: - mountPath: /host-data name: test-volume volumes: - name: test-volume hostPath: path: /data # 宿主机目录 type: Directory # 可选,“Directory”表示该目录必须存在 对于type为 FileOrCreate 模式的情况,需要注意的是,如果挂载文件有上层目录,则系统不会自动创建上层目录,当上层目录不存在时,Pod将启动失败。在这种情况下,可以将上层目录也设置为一个hostPath类型的Volume,并且设置 type为DirectoryOrCreate,确保目录不存在时,系统会将该目录自动创建出来。 下面是FileOrCreate的Pod示例,其中预先创建了文件的上层目录: --- apiVersion: v1 kind: Pod metadata: name: test-webserver spec: containers: - name: test-webserver image: k8s.gcr.io/test-webserver:latest volumeMounts: - mountPath: /var/local/aaa name: mydir - mountPath: /var/local/aaa/1.txt name: myfile volumes: - name: mydir hostPath: path: /var/local/aaa # 文件 1.txt 的上层目录 type: DirectoryOrCreate # 确保该目录存在 - name: myfile hostPath: path: /var/local/aaa/1.txt type: FileOrCreate # 确保文件存在
在Kubernetes中,对存储资源的管理方式与计算资源(CPU/内存)截然不同。为了能够屏蔽底层存储实现的细节,让用户方便使用及管理员方便管理,Kubernetes从1.0版本开始就引入了 Persistent Volume(PV) 和 Persistent Volume Claim(PVC)两个资源对象来实现存储管理子系统。
`PV(持久卷)`是对存储资源的抽象,将存储定义为一种容器应用可以使用的资源。PV由管理员创建和配置,它与存储提供商的具体实现直接相关,例如GlusterFS、iSCSI、RBD或GCE或AWS公有云提供的共享存储,通过插件式的机制进行管理,供应用访问和使用。除了EmptyDir类型的存储卷,PV的生命周期独立于使用它的Pod。
`PVC` 则是用户对存储资源的一个申请。就像 Pod 消耗 Node的资源一样,PVC消耗PV资源。PVC可以申请存储空间的大小(size)和访问模式(例如 ReadWriteOnce、ReadOnlyMany或 ReadWriteMany)。
使用PVC申请的存储空间可能仍然不满足应用对存储设备的各种需求。在很多情况下,应用程序对存储设备的特性和性能都有不同的要求,包括读写速度、并发性能、数据冗余等要求,Kubernetes从1.4版本开始引入了一个新的资源对象 StorageClass,用于标记存储资源的特性和性能,根据PVC的需求动态供给合适的PV资源。到Kubernetes 1. 6版本时,StorageClass和存储资源动态供应的机制得到完善,实现了存储卷的按需创建,在共享存储的自动化管理进程中实现了重要的一步。
通过 `StorageClass` 的定义,管理员可以将存储资源定义为某种类别(Class),正如存储设备对于自身的配置描述(Profile),例如快速存储、慢速存储、有数据冗余、无数据冗余等。用户根据 StorageClass的描述就可以直观地得知各种存储资源的特性,根据应用对存储资源的需求去申请存储资源了。
Kubernetes从1.9版本开始引人容器存储接口 Container Storage Interface(CSI)机制,目标是在Kubernetes和外部存储系统之间建立一套标准的存储管理接口,具体的存储驱动程序由存储提供商在Kubernetes之外提供,并通过该标准接口为容器提供存储服务,类似于CRI(容器运行时接口)和CNI(容器网络接口),目的是将Kubermetes 代码与存储相关代码解耦。
本节对Kubernetes的PV、PVC、StorageClass、动态资源供应和CSI等共享存储的概念、原理和应用进行详细说明。
我们可以将PV看作可用的存储资源,PVC则是对存储资源的需求。PV和PVC的生命周期如图8.1所示,其中包括资源供应(Provisioning)、资源绑定(Binding)、资源使用(Using)、资源回收(Reclaiming)几个阶段。
本节对PV和PVC生命周期中各阶段的工作原理进行说明。
# 1.资源供应
Kubernetes支持两种资源供应模式:静态模式(Static)和动态模式(Dynamic),资源供应的结果就是将适合的PV与PVC成功绑定。
· 静态模式:集群管理员预先创建许多PV,在PV的定义中能够体现存储资源的特性。
· 动态模式:集群管理员无须预先创建PV,而是通过StorageClass的设置对后端存储资源进行描述,标记存储的类型和特性。用户通过创建PVC对存储类型进行申请,系统将自动完成PV的创建及与PVC的绑定。如果PVC声明的Class为空"",则说明 PVC 不使用动态模式。另外,Kubernetes支持设置集群范围内默认的StorageClass 设置,通过 kube-apiserver开启准入控制器 DefaultStorageClass,可以为用户创建的PVC设置一个默认的存储类 StorageClass。
下面通过两张图分别对静态资源供应模式和动态资源供应模式下,PV、PVC、StorageClass及Pod使用PVC的原理进行说明。
图8.2描述了静态资源供应模式下,通过PV和PVC完成绑定并供Pod使用的原理。
图8.3描述了动态资源供应模式下,通过 StorageClass和PVC完成资源动态绑定(系统自动生成PV),并供Pod使用的原理。
# 2.资源绑定
在用户定义好PVC之后,系统将根据PVC对存储资源的请求(存储空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC绑定,用户的应用就可以使用这个PVC了。如果在系统中没有满足PVC要求的PV,PVC则会无限期处于Pending状态,直到系统管理员创建了一个符合其要求的PV。
PV一旦与某个PVC上完成绑定,就会被这个PVC独占,不能再与其他PVC绑定了。PVC与PV的绑定关系是一对一的,不会存在一对多的情况。如果PVC申请的存储空间比PV拥有的空间少,则整个PV的空间都能为PVC所用,可能造成资源的浪费。
如果资源供应使用的是动态模式,则系统在为PVC找到合适的StorageClass后,将自动创建一个PV并完成与PVC的绑定。
# 3.资源使用
Pod需要使用存储资源时,需要在Volume的定义中引用PVC类型的存储卷,将PVC挂载到容器内的某个路径下进行使用。Volume的类型字段为“persistentVolumeClaim”,在后面的示例中再进行详细举例说明。
Pod在挂载PVC后,就能使用存储资源了。同一个PVC还可以被多个Pod同时挂载使用,在这种情况下,应用程序需要处理好多个进程访问同一个存储的问题。
关于使用中的存储对象(Storage Objiectin Use Protection)的保护机制的说明如下。
存储资源(PV、PVC)相对于容器应用(Pod)是独立管理的资源,可以单独删除。在做删除操作的时候,系统会检测存储资源当前是否正在被使用,如果仍被使用,则对相关资源对象的删除操作将被推迟,直到没被使用才会行制操作,这样可以确保资源仍被使用的的情况下不会直接删除而导致数据丢失。这个机制被称为对使用中的存储对象的保护机制(Storage Object in Use Protection)。
该保护机制适用于PVC和PV两种资源,如下所述。
1)对PVC的删除操作将等到使用它的Pod被删除之后再执行
2)对PV的删除操作将等到绑定它的PVC被删除之后再执行
# 4.资源回收 (Reclaiming) 用户在使用存储资源完毕后,可以删除PVC。与该PVC绑定的PV将被标记为“已释放”,但还不能立刻与其他PVC绑定。通过之前PVC写入的数据可能还被留在存储设备上,只有在清除这些数据之后,该PV才能再次使用。 管理员可以对PV设置资源回收策略(Reclaim Policy),可以设置3种回收策略:Retain、Delete和Recycle。 1)Retain(保留数据) Retain 策略表示在删除PVC之后,与之绑定的PV不会被删除,仅被标记为已释放(released)。PV中的数据仍然存在,在清空之前不能被新的PVC使用,需要管理员手工清理之后才能继续使用,清理步骤如下。 (1)删除PV 资源对象,此时与该PV关联的某些外部存储提供商(例如AWSElasticBlockStore、GCEPersistentDisk、AzureDisk、Cinder等)的后端存储资产(asset)中的数据仍然存在。 (2)手工清理PV后端存储资产(asset)中的数据。 (3)手工删除后端存储资产。如果希望重用该存储资产,则可以创建一个新的PV与之关联。 2)Delete(删除数据) Delete 策略表示自动删除PV资源对象和相关后端存储资产,并不是所有类型的存储提供商都支持Delete策略,目前支持Delete策略的存储提供商包括AWSElasticBlockStore、GCEPersistentDisk、Azure Disk、Cinder等。 通过动态供应机制创建的PV将继承 StorageClass 的回收策略,默认为Delete策略。管理员应该基于用户的需求设置 StorageClass 的回收策略,或者在创建出 PV 后手工更新其回收策略。 3)Recycle(弃用) 目前只有 HostPort和NFS类型的 Volume 支持 Recycle策略,其实现机制为运行 rm -rf /thevolume/*命令,删除Volume目录下的全部文件,使得PV可以被新的PVC使用。 此外,管理员可以创建一个专门用于回收 HostPort或NFS类型的PV数据的自定义pod来实现数据清理工作,这个Pod的YAML 配置文件所在的目录需要通过 kube-controller-mnager 服务的启动参数 --pv-recycler-pod-template-filepath-hostpath 或 --pv-recycler-pod-template-filepath-nfs 进行设置(还可以设置相应的timeout参数)。在这个目录下创建一个Pod的YAML文件,示例如下: --- apiVersion: v1 kind: Pod metadata: name: pv-recycler namespace: default spec: restartPolicy: Never volumes: - name: vol hostPath: path: <some-path> containers: - name:pv-recycler image:busybox command: ["/bin/sh","-c","test-e/scrub && rm-rf/scrub/..?*/scrub/.[!.]*/scrub/°6& test-z \"$ (1s -A/scrub) \" 11 exit 1"] volumeMounts: - name:vol mountPath: /scrub 经过这个自定义Pod的设置,系统将通过创建这个Pod来完成PV的数据清理工作,完成PV的回收。 注意,Recycle策略已被弃用,推荐以动态供应机制管理容器所需的存储资源。
# 5.PVC资源扩容 PVC在首次创建成功之后,还应该能够在使用过程中实现空间的扩容,对PVC扩容机制的支持到Kubernetes 1.11版本时达到Beta阶段。 目前支持 PVC 扩容的存储类型有 AWSElasticBlockStore、AzureFile、AzureDisk、Cinder、FlexVolume、GCEPersistentDisk,Glusterfs、Portworx Volumes、RBD和CSI等。 如需扩容 PVC,则首先需要在PVC 对应的 StorageClass 定义中设置 allowVolumeExpansion=true,例如: --- apiVersion:storage.k8s.io/v1 kind: StorageClass metadata: name:gluster-vol-default provisioner:kubernetes.io/glusterfs parameters: resturl: "http://192.168.10.100:8080" restuser: "" secretNamespace: "" secretName: "" allowVolumeExpansion:true 对PVC进行扩容操作时,只需修改PVC的定义,将resources.requests.storage 设置为一个更大的值即可,例如通过以下设置,系统将会基于PVC 新设置的存储空间触发后端PV的扩容操作,而不会创建一个新的PV资源对象: --- resources: requests: storage: 16Gi 此外,存储资源扩容还存在以下几种情况。 (1)CSI类型存储卷的扩容。对于CSI类型存储卷的扩容,在Kubernetes 1. 16版本时达到Beta阶段,同样要求CSI存储驱动能够支持扩容操作,请参考各存储提供商的CSI驱动的文档说明。 (2)包含文件系统(File System)存储卷的扩容。对于包含文件系统存储卷的扩容,文件系统的类型必须是XFS、Ext3或Ext4,同时要求Pod使用PVC时设置的是可读可写(ReadWrite)模式。文件系统的扩容只能在Pod启动时完成,或者底层文件系统在Pod运行过程中支持在线扩容。对于FlexVolume 类型的存储卷,在驱动程序支持 RapuiresFSResize=true 参数设置的情况下才支持扩容。另外,FlexVolume支持在Pod重启时完成扩容操作。 (3)使用中的PVC在线扩容。Kubemetes从1.11版本开始引入了对使用中的PVC进厅在线扩容的支持,到1.15版本时达到Beta阶段,以实现扩容PVC时无须重建Pod.为使用该功能,需要设置 kube-apiserver、kube-controller-manager、kbelet服务的启动参 --feamurc-gates=ExpandinUsePersistentVolumes=true 来开启该特性开关。PVC在线扩容机要求使用了PVC的Pod成功运行,对于没被任何Pod使用的PVC,不会有实际的扩容果。FlexVolume类型的存储卷也可以在Pod使用时在线扩容,这需要底层存储驱动提供支持。 (4)扩容失败的恢复机制。如果扩容存储资源失败,则集群管理员可以手工恢复PVC的状态并且取消之前的扩容请求,否则系统将不断尝试扩容请求。执行恢复操作的步骤:设置与PVC绑定的PV资源的回收策略为“Retain”;删除PVC,此时PV的数据仍然存在;删除PV中的 claimRef 定义,这样新的PVC可以与之绑定,结果将使得PV的状态为“Available”;新建一个PVC,设置比PV空间小的存储空间申请,同时设置 volumeName字段为PV的名称,结果将使得PVC与PV完成绑定;恢复PVC的原回收策略。
百度https://www.cnblogs.com/rexcheny/p/10925464.html
PV作为对存储资源的定义,主要涉及存储能力、访问模式、存储类型、回收策略、后端存储类型等关键信息的设置。 下面的示例声明的PV具有如下属性:5GiB存储空间,存储卷模式为Filesystem,访问模式为 ReadWriteOnce,存储类型为slow(要求在系统中已存在名称为“slow”的StorageClass),回收策略为 Recycle,并且后端存储类型为nfs(设置了NFS Server的IP地址和路径),同时设置了挂载选项(mountOptions)。 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp server: 172.17.0.2 Kubernetes支持的PV类型如下。 · AWSElasticBlockStore:AWS公有云提供的Elastic Block Store。 · AzureFile:Azure公有云提供的File。 · AzureDisk:Azure 公有云提供的Disk。 · CephFS:一种开源共享存储系统。 · Cinder:OpenStack块存储系统。 · FC(Fibre Channel):光纤存储设备。 · Flex Volume:一种插件式的存储机制。 · Flocker:一种开源共享存储系统。 · GCEPersistentDisk:GCE公有云提供的 Persistent Disk。 · Glusterfs:一种开源共享存储系统。 · HostPath:宿主机目录,仅用于单机测试。 · iSCSI:iSCSI存储设备。 · Local:本地存储设备,从Kubernetes 1.7版本开始引入,到1.14版本时达到稳定版本,目前可以通过指定块设备(Block Device)提供Local PV,或通过社区开发的 sig-storage-local-static-provisioner插件管理Local PV的生命周期。 · NFS:网络文件系统。 · OPortworx Volumes:Portworx 提供的存储服务。 · Quobyte Volumes:Quobyte提供的存储服务。 · RBD(Ceph Block Device):Ceph块存储。 · ScalelO Volumes:DellEMC的存储设备。 · StorageOS:StorageOS提供的存储服务。 · OVsphereVolume:VMWare提供的存储系统。 每种存储类型都有各自的特点,在使用时需要根据它们各自的参数进行设置。 PV资源对象需要设置的关键配置参数如下。 # 1.存储容量(Capacity) 指述存储的容量,目前仅支持对存储空间的设置 ( storage=xx ),未来可能加入IOPS、吞吐率等设置。 # 2.存储卷模式(Volume Modes) Kubernetes从1.13版本开始引入存储卷类型的设置( volumeMode=xxx ),到1.18版本时达到稳定阶段。 可以设置的选项包括Filesystem(文件系统,默认值)和Block(块设备)。文件系统模式的PV将以目录(Directory)形式挂载到Pod内。如果模式为块设备,但是设备是空的,则Kubernetes会自动在块设备上创建一个文件系统。支持块设备的存储类型会以裸设备(Raw Block Device)的形式挂载到容器内,并且不会创建任何文件系统,适用于需要直接操作裸设备(速度最快)的应用程序。 目前有以下PV类型支持裸块设备类型:AWSElasticBlockStore、AzureDisk、FC(Fibre Channel) 、GCEPersistentDisk、iSCSI、Local volume、OpenStack Cinder、RBD (Ceph BlockDevice) 、Vsphere Volume。 下面的示例使用了块设备的PV定义: --- apiVersion: v1 kind: PersistentVolume metadata: name: block-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain volumeMode: Block fc: targetWWNs: ["50060e801049cfd1"] lun: 0 readOnly: false # 3.访问模式(Access Modes) PV存储卷在挂载到宿主机系统上时,可以设置不同的访问模式(Access Modes)。PV支持哪些访问模式由存储提供商提供支持,例如NFS可以支持多个客户端同时读写(ReadWriteMany)模式,但一个特定的 NFS PV 也可以以只读(Read-only)模式导出到服务器上。 Kubernetes支持的访问模式如下。 · ReadWriteOnce(RWO):读写权限,并且只能被单个Node挂载。 · ReadOnlyMany(ROX):只读权限,允许被多个Node挂载。 · ReadWriteMany(RWX):读写权限,允许被多个Node挂载。 某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问模式,多种访问模式不能同时生效。 表8.2描述了不同的存储提供者支持的访问模式。
# 4.存储类别(Class) PV可以设定其存储的类别,通过storageClassName参数指定一个StorageClass 资源对象的名称。具有特定类别的PV只能与请求了该类别的PVC绑定。未设定类别的PV则只能与不请求任何类别的PVC绑定。 #5. 回收策略(Reclaim Policy) 通过PV定义中的 persistentVolumeReclaimPolicy 字段进行设置,可选项如下。 · Retain:保留数据,需要手工处理。 · Recycle:简单清除文件的操作(例如运行rm-rf/thevolume/*命令)。 · Delete:与PV相连的后端存储完成Volume的删除操作。 目前只有NFS和HostPath两种类型的PV支持 Recycle 策略;AWSElasticBlockStore、GCEPersistentDisk、AzureDisk和Cinder类型的PV支持 Delete策略。 # 6.挂载选项(Mount Options) 在将 PV挂载到一个Node上时,根据后端存储的特点,可能需要设置额外的挂载选的参数,这个可以在PV定义中的moumtOptions字段进行设置。下面的例子为对一个类型为BePersistentDisk的PV设置挂载选项的参数: --- apiVersion: "v1" kind: "PersistentVolume" metadata: name: gce-disk-1 spec: capacity: storage: "10Gi" accessModes: - "ReadWriteOnce" mountOptions: - hard - nolock - nfsvers=3 gcePersistentDisk: fsType: "ext4" pdName: "gce-disk-1" 目前,以下PV类型支持设置挂载选项:AWSElasticBlockStore、AzureDisk、AzureFile、CephFS、Cinder (OpenStack block storage) 、GCEPersistentDisk、Glusterfs、NFS、QuobyteVolumes、RBD (CephBlock Device) 、StorageOS、VsphereVolume、iSCSI。 注意,Kubernetes不会对挂载选项进行验证,如果设置了错误的挂载选项,则挂载将会失败。 # 7.节点亲和性(Node Affinity) PV可以设置节点亲和性来限制只能通过某些 Node 访问 Volume,可以在PV定义的nodeAffinity字段中进行设置。使用这些Volume的Pod将被调度到满足条件的Node上。 公有云提供的存储卷(如AWSElasticBlockStore、GCEPersistentDisk、AzureDisk等)都由公有云自动完成节点亲和性设置,无须用户手工设置。对于Local 类型的PV,需要手工设置,例如: --- apiVersion: v1 kind: PersistentVolume metadata: name: example-local-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /mnt/disks/ssd1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - my-node 某个PV在生命周期中可能处于以下4个阶段(Phase)之一。 · Available:可用状态,还未与某个PVC绑定。 · Bound:已与某个PVC绑定。 · Released:与之绑定的PVC已被删除,但未完成资源回收,不能被其他PVC使用。 · Failed:自动资源回收失败。 在定义了PV资源之后,就需要通过定义PVC来使用PV资源了。
PVC作为用户对存储资源的需求申请,主要涉及存储空间请求、访问模式、PV选择条件和存储类别等信息的设置。 下例声明的PVC具有如下属性:申请8GiB存储空间,访问模式为ReadWriteOnce,PV 选择条件为包含 release=stable 标签并且包含条件为 environmentIn[dev]的标签,存储类别为“slow”(要求在系统中已存在名为slow的StorageClass)。 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]} 对PVC的关键配置参数说明如下。 (1)资源请求(Resources):描述对存储资源的请求,通过 resources.requests.storage字段设置需要的存储空间大小。 (2)访问模式(Access Modes):PVC也可以设置访问模式,用于描述用户应用对存储资源的访问权限。其三种访问模式的设置与PV的设置相同。 (3)存储卷模式(Volume Modes):PVC也可以设置存储卷模式,用于描述希望使用的PV存储卷模式,包括文件系统(Filesystem)和块设备(Block)。PVC设置的存储卷模式应该与PV存储卷模式相同,以实现绑定;如果不同,则可能出现不同的绑定结果。在各种组合模式下是否可以绑定的结果如表8.3所示。 (4)PV选择条件(Selector):通过 Label Selector的设置,可使PVC对于系统中已存在的各种PV进行筛选。系统将根据标签选出合适的PV与该PVC进行绑定。对选择条件可以使用matchLabels和matchExpressions 进行设置,如果两个字段都已设置,则Selector的逻辑将是两组条件同时满足才能完成匹配。 (5)存储类别(Class):PVC 在定义时可以设定需要的后端存储的类别(通过storageClassName 字段指定),以减少对后端存储特性的详细信息的依赖。只有设置了该Class的PV才能被系统选出,并与该PVC进行绑定。PVC也可以不设置Class需求,如果storageClassName被设置为空 ( storageClassName="" ),则表示该PVC不要求特定的Class,系统将只选择未设定Class的PV与之匹配和绑定。PVC也可以完全不设置storageClassName字段,此时将根据系统是否启用了名为DefaultStorageClass的admissioncontroller进行相应的操作。 · 启用DefaultStorageClass:要求集群管理员已定义默认的StorageClass.如果在系统中不存在默认的StorageClass,则等效于不启用DefaultStorageClass的情况。如果存在默认的StorageClass,则系统将自动为PVC 创建一个PV(使用默认StorageClass的后端存储),并将它们进行绑定。集群管理员设置默认 StorageClass时,会在 StorageClass 的定义中加上一个 annotation“storageclass.kubernetes.io/is-default-class=true”。如果管理员将多个StorageClass都定义为default,则由于不唯一,系统将无法创建PVC。 · 未启用 DefaultStorageClass:等效于 PVC 设置 storageClassName 的值为空(storageClassName=""),即只能选择未设定Class的PV与之匹配和绑定。当 Selector和Class 都进行了设置时,系统将选择两个条件同时满足的PV与之匹配。另外,如果PVC设置了Selector,则系统无法使用动态供给模式为其分配PV。
在PVC创建成功之后,Pod就可以以存储卷(Volume)的方式使用PVC的存储资源了。PVC受限于命名空间,Pod在使用PVC时必须与PVC处于同一个命名空间。 `Kubernetes为Pod挂载PVC的过程如下:系统在Pod所在的命名空间中找到其配置的PVC,然后找到PVC绑定的后端PV,将PV存储挂载到Pod所在Node的目录下,最后将Node的目录挂载到Pod的容器内。` 在Pod中使用PVC时,需要在YAML配置中设置PVC类型的Volume,然后在容器volmeMouns.mountPath设置容器内的挂载目录,示例如下: --- apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/var/www/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim 如果存储卷模式为块设备(Block),则 PVC 的配置与默认模式 (Filesystem)略有不同。下面对如何使用裸块设备(Raw Block Device)进行说明。 假设使用裸块设备的 PV 已创建,例如: --- apiVersion: v1 kind: PersistentVolume metadata: name: block-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce volumeMode: Block persistentVolumeReclaimPolicy: Retain fc: targetWWNs: ["50060e801049cfd1"] lun: 0 readOnly: false PVC 的 YAML 配置示例如下: --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: block-pvc spec: accessModes: - ReadWriteOnce volumeMode: Block resources: requests: storage: 10Gi 使用裸块设备 PVC 的 Pod 定义如下。与文件系统模式 PVC 的用法不同,容器不使用 volumeMounts 设置挂载目录,而是通过 volumeDevices 字段设置块设备的路径 devicePath : --- apiVersion: v1 kind: Pod metadata: name: pod-with-block-volume spec: containers: - name: fc-container image: fedora:26 command: ["/bin/sh", "-c"] args: [ "tail -f /dev/null" ] volumeDevices: - name: data devicePath: /dev/xvda volumes: - name: data persistentVolumeClaim: claimName: block-pvc 在某些应用场景中,同一个Volume可能会被多个Pod或者一个Pod中的多个容器共享,此时可能存在各应用程序需要使用不同子目录的需求。这可以通过Pod的volumeMounts定义的subPath字段进行设置。通过对subPath的设置,在容器中将以subPath设置的目录而不是在Volume中提供的默认根目录作为根目录使用。 下面的两个容器共享同一个PVC(及后端PV),但是各自在Volume中可以访问的根目录由subPath进行区分,mysql容器使用Volume中的mysql子目录作为根目录,php容器使用Volume中的html子目录作为根目录: --- apiVersion: v1 kind: Pod metadata: name: mysql spec: containers: - name: mysql image: mysql env: - name: MYSQL_ROOT_PASSWORD value: "rootpasswd" volumeMounts: - mountPath: /var/lib/mysql name: site-data subPath: mysql - name: php image: php:7.0-apache volumeMounts: - mountPath: /var/www/html name: site-data subPath: html volumes: - name: site-data persistentVolumeClaim: claimName: site-data-pvc 注意,subPath中的路径名称不能以“/”开头,需要用相对路径的形式。 在一些应用场景中,如果希望通过环境变量来设置 subPath路径,例如使用Pod名称作为子目录的名称,则可以通过subPathExpr字段提供支持。subPathExpr字段用于将Downward API的环境变量设置为存储卷的子目录,该特性在Kubernetes 1.17版本时达到稳定阶段。 需要注意的是,subPathExpr字段和subPath字段是互斥的,不能同时使用。 下面的例子通过Downward API将Pod名称设置为环境变量 POD_NAME,然后在挂载存储卷时设置 subPathExpr=$(POD_NAME) 子目录: --- apiVersion: v1 kind: Pod metadata: name: pod1 spec: containers: - name: container1 env: - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name image: busybox command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ] volumeMounts: - name: workdir1 mountPath: /logs subPathExpr: $(POD_NAME) restartPolicy: Never volumes: - name: workdir1 hostPath: path: /var/log/pods
StorageClass 作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定,实现动态的资源供应。基于StorageClass的动态资源供应模式将逐步成为云平台的标准存储管理模式。 StorageClass 资源对象的定义主要包括名称、后端存储的提供者(provisioner)、后端存储的相关参数配置和回收策略。StorageClass的名称尤为重要,将在创建PVC时引用,管理员应该准确命名具有不同存储特性的 StorageClass。 StorageClass一旦被创建,则无法修改。如需更改,则只能删除原 StorageClass 资源对象并重新创建。 下例定义了一个StorageClass,名称为standard,provisioner 为aws-ebs,type为gp2,回收策略为Retain等: --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 reclaimPolicy: Retain allowVolumeExpansion: true mountOptions: - debug volumeBindingMode: Immediate StorageClass 资源对象需要设置的关键配置参数如下。 # 1.存储提供者(Provisioner) 描述存储资源的提供者,用于提供具体的PV资源,也可以将其看作后端存储驱动。目前,Kubernetes 内置支持的Provisioner包括 AWSElasticBlockStore、AzureDisk、AzureFile、Cinder (OpenStack Block Storage) 、Flocker、GCEPersistentDisk、GlusterFS、Portworx Volume、Quobyte Volumes、RBD (Ceph Block Device) 、ScaleIO、StorageOS、VsphereVolume。 Kubernetes内置支持的Provisioner的命名都以“kubernetes.io/”开头,用户也可以使用自定义的后端存储提供者。为了符合StorageClass的用法,自定义Provisioner需要符合存储卷的开发规范。外部存储供应商的作者对代码、提供方式、运行方式、存储插件(包括Flex)等具有完全的自由控制权。目前,在Kubernetes的 kubernetes-sigs/sig-storage-lib-external-provisioner 库中维护外部 Provisioner的代码实现,其他一些 Provisioner 也在kubernetes-incubator/external-storage库中进行维护。 例如,对NFS类型,Kubernetes 没有提供内部的 Provisioner,但可以使用外部的Provisioner.也有许多第三方存储提供商自行提供外部的Provisioner。 # 2.资源回收策略(Reclaim Policy) 通过动态资源供应模式创建的PV将继承在StorageClass资源对象上设置的回收策略,配置字段名称为“reclaimPolicy”,可以设置的选项包括Delete(删除)和Retain(保留)。 如果 StorageClass 没有指定 reclaimPolicy,则默认值为Delete. 对于管理员手工创建的仍被 StorageClass管理的PV,将使用创建PV时设置的资源回收策略。 # 3.是否允许存储扩容(Allow Volume Expansion) PV可以被配置为允许扩容,当 StorageClass 资源对象的 allowVolumeExpansion字段被设置为true时,将允许用户通过编辑PVC的存储空间自动完成PV的扩容,该特性在Kubernetes1.11版本时达到Beta阶段。 表8.4描述了支持存储扩容的Volume类型和要求的Kubermetes 最低版本。
# 4.挂载选项(Mount Options) 通过 StorageClass 资源对象的 mountOptions字段,系统将为动态创建的PV设置挂载选项。并不是所有PV类型都支持挂载选项,如果PV不支持但 StorageClass设置了该字段,PV将会创建失败。另外,系统不会对挂载选项进行验证,如果设置了错误的选项,则容器在挂载存储时将直接失败。 # 5.存储绑定模式(Volume Binding Mode) StorageClass 资源对象的 volumeBindingMode 字段设置用于控制何时将PVC与动态创建的PV绑定。目前支持的绑定模式包括 Immediate和WaitForFirstConsumer。存储绑定模式的默认值为Immediate,表示当一个PersistentVolumeClaim(PVC)创建,就动态创建 PV 并进行 PVC 与 PV 的绑定操作。需要注意的是,对于拓扑受限(Topology-limited)或无法从全部 Node 访问后端存储,将在不了解 Pod 调度需求的情况下完成 PV 的绑定操作,这可能会导致某些Pod无法完成调度。 WaitForFirstConsumer绑定模式表示PVC与PV的绑定操作延迟到第一个使用PVC的Pod创建出来时再进行。系统将根据Pod的调度需求,在Pod所在的Node上创建Pv,这些调度需求可以通过以下条件(不限于)进行设置: · Pod对资源的需求; · Node Selector; · Pod亲和性和反亲和性设置; · Taint和Toleration 设置。 目前支持 WaitForFirstConsumer 绑定模式的存储卷包括:AWSElasticBlockStore、AzureDisk、GCEPersistentDisk。 另外,有些存储插件通过预先创建好的PV绑定支持 WaitForFirstConsumer模式,比如AWSElasticBlockStore、AzureDisk、GCEPersistentDisk和Local。 在使用 WaitForFirstConsumer 模式的环境中,如果仍然希望基于特定拓扑信息(Topology)进行PV绑定操作,则在StorageClass的定义中还可以通过 allowedTopologies字段进行设置。下面的例子通过 matchLabelExpressions 设置目标 Node的标签选择条件(zone=us-centrall-a或us-centrall-b),PV将在满足这些条件的Node上允许创建: --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/gce-pd parameters: type: pd-standard volumeBindingMode: WaitForFirstConsumer allowedTopologies: - matchLabelExpressions: - key: failure-domain.beta.kubernetes.io/zone values: - us-central1-a - us-central1-b # 6.存储参数(Parameters) 后端存储资源提供者的参数设置,不同的Provisioner可能提供不同的参数设置。某些参数可以不显示设定,Provisioner将使用其默认值。目前StorageClass 资源对象支持设置的存储参数最多为512个,全部 key和value所占的空间不能超过256KiB. 下面是一些常见存储提供商(Provisioner)提供的StorageClas存销参数示例。 (部分示例,具体详见P596) 1)AWSElasticBlockStore 存储卷 --- kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: slow provisioner: kubernetes.io/aws-ebs parameters: type: io1 iopsPerGB: "10" fsType: ext4 可以配置的参数如下(详细说明请参考AWSElasticBlockStore文档)。 · type:可选项为io1、gp2、scl、stl,默认值为gp2。 · iopsPerGB:仅用于io1类型的Volume,意为每秒每GiB的I/O操作数量。 · fsType:文件系统类型,默认值为ext4。 · encrypted:是否加密。 · kmsKeyId:加密时使用的Amazon Resource Name. # 7.设置默认的 StorageClass 在Kubernetes中,管理员可以为有不同存储需求的PVC创建相应的 StorageClass 来提供动态的存储资源(PV)供应,同时在集群级别设置一个默认的 StorageClass ,为那些未指定 StorageClass 的PVC使用。当然,管理员要明确系统默认提供的 StorageClass 和符合PVC的资源需求,同时注意避免资源浪费。 要在集群中启用默认的 StorageClass,就需要在 kube-apiserver 服务准入控制器 --enable-admission-plugins 中开启 DefaultStorageClass (从Kubernetes 1. 10版本开始默认开启): --enable-admission-plugins=...,DefaultStorageClass 然后,在StorageClass的定义中设置一个annotation: --- kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: gold annotations: storageclass.beta.kubernetes.io/is-default-class="true" provisioner: kubernetes.io/gce-pd parameters: type: pd-ssd 通过 kubectl create 命令创建成功后,查看 StorageClass 列表,可以看到名为gold 的 StorageClass 被标记为 default:
本节讲解 Node 管理方面的内容
在硬件升级、维护等情况下,我们需要将某些Node隔离,使其脱离 Kubernetes 的调度范围。Kubernetes提供了一种机制,既可以将Node纳入调度范围,也可以将Node脱离调度范围。我们可以使用 YAML 文件或者 kubectl 命令进行调整,示例如下。 # 1.使用YAML文件 创建配置文件 unschedule_node.yaml,在spec 部分指定 unschedulable为true: --- apiVersion: v1 kind: Node metadata: name: k8s-node-1 labels: kubernetes.io/hostname: k8s-node-1 spec: unschedulable: true 执行 kubectl replace 命令,完成对Node状态的修改: $ kubectl replace -f unschedule_node.yaml 查看Node的状态,可以观察到其中增加了一项 SchedulingDisabled: $ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-node-1 Ready,SchedulingDisabled <none> 1h VERSIONv1.19.0 这样,系统就不会将后续创建的Pod调度向该Node了。每果需要将某个Nodc重新纳入集群调度范围,则将 unschcdulable 设置为false,再次运行 kubectl replace 命令,就能恢复系统对该Node的调度了。 # 2.使用 kubectl patch 命令 我们也可以直接运行 kubectl patch 命令实现 Node 隔离调度的效果,不使用配置文件: $ kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' $ kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":false}}' # 3.使用 kubectl cordon 和 uncordon 命令 另外,使用 kubectl 子命令 cordon 和 uncordon 也可以实现Node的隔离调度和恢复调度。例如,运行 kubectl cordon <node_name> 命令对某个Node进行隔离调度操作: $ kubectl cordon k8s-node-1Node 又如,运行 kubectl uncordon <node_name> 命令对某个Node进行恢复调度操作: $ kubectl uncordon k8s-node-1Node #需要注意的是,某个Node脱离调度范围时,其上运行的Pod并不会自动停止,用户需要对其手动停止。
在实际生产系统中经常会出现服务器容量不足的情况,这时就需要购买新的服务器,然后将应用系统进行水平扩展来完成对系统的扩容。
`在 Kubernetes 集群中,一个新Node的加入是非常简单的。在新的Node上安装 docker、kubelet 和 kube-proxy 服务,然后配置 kubelet 和 kube-proxy 服务的启动参数,将 Master URL 指定为当前 Kubernetes 集群 Master 的地址,最后启动这些服务。通过 kubelet 服务默认的自动注册机制,新的Node将自动加入现有的Kubernetes集群中,如图10.1所示。`
Kubernetes Master在接受新Node的注册之后,会自动将其纳入当前集群的调度范围,之后创建容器时就可以对新的Node进行调度了。
通过这种机制,Kubernetes实现了集群中Node的扩容。
Label(标签) 是用户可灵活定义的对象属性,对于正在运行的资源对象,我们随时可以通过 kubectl label 命令进行增加、修改、删除等操作,示例如下。
(1)给已创建的Pod "redis-master-bobr0” 添加一个 Lable "role=backend" :
$ kubectl label pod redis-master-bobr0 role=backend
(2)查看该Pod的Label:
$ kubectl get pods -Lrole
(3)删除一个Label,只需在命令行最后指定`Label的key名`并与一个减号相连即可:
$ kubectl label pod redis-master-bobr0 role-
(4)修改一个Label的值,需要加上 --overwrite 参数:
$ kubectl label pod redis-master-bobr0 role-master --overwrite
在一个组织内部,不同的工作组可以在同一个 Kubernetes 集群中工作,Kubernetes 通过 Namespace(命名空间)和 Context 的设置对不同的工作组进行区分,使得它们既可以共享同一个 Kubernetes 集群的服务,也可以互不干扰,如图10.2所示。
假设在我们的组织中有两个工作组:开发组和生产运维组。开发组在 Kubernetes 集群中需要不断创建、修改及删除各种 Pod、RC、Service 等资源对象,以便实现敏捷开发。生产运维组则需要通过严格的权限设置来确保生产系统中的 Pod、RC、Service 处于正常运行状态且不会被误操作。
为了在Kubernetes集群中实现这两个分组,首先需要创建两个命名空间。 创建命名空间1: # namespace-development.yaml --- apiVersion: v1 kind: Namespace metadata: name: development 创建命名空间2: # namespace-production.yaml --- apiVersion: v1 kind: Namespace metadata: name: production 使用 kubectl create 命令完成命名空间的创建: $ kubectl create -f namespace-development.yaml $ kubectl create -f namespace-production.yaml 查看系统中的命名空间: $ kubectl get namespaces
# 百度
通过 kubeconfig 文件中的 context 元素,使用简便的名称来对访问参数进行分组。每个上下文都有三个参数:cluster、namespace 和 user。默认情况下,kubectl 命令行工具使用 当前上下文 中的参数与集群进行通信。
kubectl config 命令可以设置和使用 context
$ kubectl config --help
接下来,需要为这两个工作组分别定义一个 Context,即运行环境。这个运行环境将属于某个特定的命名空间。 通过 kubectl config set-context 命令定义Context,并将Context置于之前创建的命名空间中: $ kubectl config set-context kubernetes-cluster --server=https://192.168.1.128:8080 Cluster "kubernetes-cluster" set. $ kubectl config set-context ctx-dev --namespace=development --cluster=kubernetes-cluster --user=dev Context "ctx-dev" created. $ kubectl config set-context ctx-prod --namespace=production --cluster=kubernetes-cluster --user=prod Context "ctx-prod" created. 通过 kubectl config view 命令查看已定义的Context: $ kubectl config view apiVersion:v1clusters: - cluster: server:http: //192. 168. 1. 128: 8080 name:kubernetes-cluster contexts: - context: cluster:kubernetes-cluster namespace:development user:dev name:ctx-dev - context: cluster:kubernetes-cluster namespace:production user: prod name:ctx-prod current-context: "" kind: Config preferences: {} users:null 通过 kubectl config 命令在 ${HOME}/.kube 目录下生成了一个名为 “config” 的文件,文件的内容即 kubectl config view 命令显示的内容。所以,也可以通过手工编辑该文件的方式来设置 Context。
我们可以通过 kubecl config use-context <context name> 命令设置当前运行环境。通过下面的命令将把当前运行环境设置为ctx-der: $ kubectl config use-context ctx-dev switched tocontext"ctx-dev". 运行这个命令后,当前运行环境被设置为开发组所需的环境。之后的所有操作都将在名为derelopment的命名空间中完成。现在,以 redis-slave RC 为例创建两个Pod: # redis-slave-controller.yaml --- apiVersion: v1 kind: ReplicationController metadata: name: redis-slave labels: name: redis-slave spec: replicas: 2 selector: name: redis-slave template: metadata: labels: name: redis-slave spec: containers: - name: slave image: kubeguide/guestbook-redis-slave ports: - containerPort: 6379 $ kubectl create -f redis-slave-controller.yaml 查看创建好的Pod $ kubectl get pods NAME READY STATUS RESTARTS AGE redis-slave-0feq9 1/1 Running 0 6m redis-slave-6i0g4 1/1 Running 0 6m 可以看到容器被正确创建并运行起来了。而且,由于当前运行环境是ctxdev,所以不会影响生产运维组的工作。 切换到生产运维组的运行环境中: $ kubectl config use-context ctx-prod Switched to context "ctx-prod". 查看RC和Pod: $ kubectl get rc CONTROLLER CONTAINER (S) IMAGE (S) SELECTOR REPLICAS $ kubectl get pods NAME READYSTATUS RESTARTS AGE 结果为空,说明看不到开发组创建的RC和Pod了。 现在也为生产运维组创建两个 redis-slave 的Pod: $ kubectl create -f redis-slave-controller.yaml replicationcontrollers/redis-slave created 至此,我们为两个工作组分别设置了两个运行环境,设置好当前运行环境时,各工作组之间的工作将不会相互干扰,并且都能在同一个Kubernetes集群中同时工作。
在Kubernetes集群中,节点最重要的资源包括CPU、内存和磁盘,其中,内存和磁盘资源属于不可压缩的资源,如果这类资源不足,则无法继续申请新的资源。同时,节点中现存的进程,包括操作系统的进程、用户进程(含Pod进程),随时可能申请更多的内存或磁盘资源,所以在资源严重不足的情况下,操作系统会触发 OOM Killer 的终极审批。
OOM Killer (Out-Of-Memory killer)的机制,该机制主要用于内存监控,监控进程的内存使用量,当系统的内存耗尽时,其将根据算法选择性地 kill 本文分析的内存溢出保护机制,也就是OOM killer机制了。
为了避免出现这种严重后果,Kubernetes设计和实现了一套自动化的Pod驱逐机制,该机制会自动从资源紧张的节点上驱逐一定数量的Pod,以保证在该节点上有充足的资源。具体做法是通过kubelet实现Pod的驱逐过程,而kubelet也不是随机驱逐的,它有自己的一套驱逐机制,每个节点上的kubelet都会通过 cAdvisor 提供的资源使用指标来监控自身节点的资源使用量,并根据这些指标的变化做出相应的驱逐决定和操作。kubelet持续监控主机的资源使用情况,尽量防止计算资源被耗尽,一旦出现资源紧缺的迹象,就会主动终止一个或多个Pod的运行,以回收紧缺的资源。当一个Pod被终止时,其中的容器会被全部停止,Pod的状态会被设置为Failed。
书中内容太多,下面内容百度的https://blog.csdn.net/qq_31136839/article/details/100674965(部分内容)
如何在系统硬件资源紧缺的情况下保证Node的稳定性,是kubelet需要解决的一个重要问题。尤其对于内存和磁盘这种不可压缩的资源,紧缺就意味着不稳定。下面对驱逐的策略、信号、阈值、监控频率和驱逐操作进行详细说明。 #驱逐策略 kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗尽。一旦出现资源紧缺的迹象,kubelet就会主动终止一个或多个Pod的运行,以回收紧缺的资源。当一个Pod被终止时,其中的容器会全部停止,Pod的状态会被设置为Failed。 #驱逐信号 在下表中提到了一些信号,kubelet能够利用这些信号作为决策依据来触发驱逐行为。其中,描述列中的内容来自kubelet Summary API;每个信号都支持整数值或者百分比的表示方法,百分比的分母部分就是各个信号相关资源的总量。 --- 驱逐信号 描 述 memory.available memory.available:=node.status.capacity[memory] -node.stats.memory.workingSet nodefs.available nodefs.available:=node.stats.fs.available nodefs.inodesFree nodefs.inodesFree:=node.stats.fs.inodesFree imagefs.available imagefs.available:=node.stats.runtime.imagefs.available imagefs.inodesFree imagefs.inodesFree:=node.stats.runtime.imagefs.inodesFree --- memory.available的值取自cgroupfs,而不是free -m命令,这是因为free -m不支持在容器内工作。如果用户使用了node allocatable功能,则除了节点自身的内存需要判断,还需要利用cgroup根据用户Pod部分的情况进行判断。 --- #!/bin/bash #!/usr/bin/env bash # this script reproduces what the kubelet does # to calculate memory. available relative to root cgroup. # current memory usage memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}') memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024 )) memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes) memory_total_inactive_file=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}') memory_working_set=$memory_usage_in_bytes if [ "$memory_working_set" -lt "$memory_total_inactive_file"]; then memory_working_set=0 else memory_working_set=$((memory_usage_in_bytes-memory_total_inactive_file)) fi memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set )) memory_available_in_kb=$((memory_available_in_bytes / 1024 )) memory_available_in_mb=$((memory_available_in_kb / 1024 )) echo "memory.capacity_in_bytes $memory_capacity_in_bytes" echo "memory.usage_in_bytes $memory_usage_in_bytes" echo "memory.total_inactive_file $memory_total_inactive_file" echo "memory.working_set $memory_working_set" echo "memory.available_in_bytes $memory_available_in_bytes" echo "memory.available_in_kb $memory_available_in_kb" echo "memory.available in mb $memory_available_in_mb" ——— kubelet假设inactive_file(不活跃LRU列表中的file-backed内存,以字节为单位)在紧缺情况下可以回收,因此对其进行了排除。 kubelete支持以下两种文件系统。 `(1)nodefs:保存kubelet的卷和守护进程日志等。` `(2)imagefs:在容器运行时保存镜像及可写入层。` kubelet使用cAdvisor自动监控这些文件系统。kubelet不关注其他文件系统,不支持所有其他类型的配置,例如保存在独立文件系统中的卷和日志。 磁盘压力相关的资源回收机制正在逐渐被驱逐策略接管,未来会停止对现有垃圾收集方式的支持。 #驱逐阀值 kubelet 可以定义驱逐阈值,一旦超出阈值,就会触发kubelet进行资源回收操作。 阀值的定义方式为: <eviction-signal> <operator> <quantity> 其中: ◎ 在上表中列出了驱逐信号的名称。 ◎ 当前仅支持一个operator(运算符):< (小于)。 ◎ quantity需要符合Kubernetes的数量表达方式,也可以用以%结尾的百分比表示。 例如,如果一个节点有10GiB内存,我们希望在可用内存不足1GiB时进行驱逐,就可以用下面任一方式来定义驱逐阈值。 ◎ memory.available<10%。 ◎ memory.available<1GiB。 驱逐阈值又可以通过软阈值和硬阈值两种方式进行设置。 `1.驱逐软阀值` 驱逐软阈值由一个驱逐阈值和一个管理员设定的宽限期共同定义。当系统资源消耗达到软阈值时,在这一状况的持续时间达到宽限期之前,kubelet不会触发驱逐动作。如果没有定义宽限期,则kubelet会拒绝启动。 另外,可以定义终止Pod的宽限期。如果定义了这一宽限期,那么kubelet会使用pod.Spec.TerminationGracePeriodSeconds和最大宽限期这两个值之间较小的数值进行宽限,如果没有指定,则kubelet会立即杀掉Pod。 软阈值的定义包括以下几个参数。 ◎ --eviction-soft:描述驱逐阈值(例如memory.available<1.5GiB),如果满足这一条件的持续时间超过宽限期,就会触发对Pod的驱逐动作。 ◎ --eviction-soft-grace-period:驱逐宽限期(例如memory.available=1m30s),用于定义达到软阈值之后持续时间超过多久才进行驱逐。 ◎ --eviction-max-pod-grace-period:在达到软阈值后,终止Pod的最大宽限时间(单位为s)。 `2.驱逐硬阀值` 硬阈值没有宽限期,如果达到了硬阈值,则kubelet会立即杀掉Pod并进行资源回收。 硬阈值的定义包括参数--eviction-hard:驱逐硬阈值,一旦达到阈值,就会触发对Pod的驱逐操作。 kubelet的默认硬阈值定义如下: --eviction-hard=memory.available<100Mi
在Kubernetes集群运行过程中,许多管理操作都可能对Pod进行主动驱逐,“主动”一词意味着这一操作可以安全地延迟一段时间,目前主要针对以下两种场景。 · 节点维护或升级时(kubectl drain)。 · 对应用的自动缩容操作(autoscaling down)。 作为对比,由于节点不可用(Not Ready)导致的Pod驱逐就不能被称为主动了,但是Pod的主动驱逐行为可能导致某个服务对应的Pod实例全部或大部分被“消灭”,从而引发业务中断或业务SLA降级,而这是违背 Kubernetes 的设计初衷的。因此需要一种机制来避免我们希望保护的Pod被主动驱逐,这种机制的核心就是 PodDisruptionBudget。通过使用 PodDisruptionBudget,应用可以保证那些会主动移除Pod的集群操作永远不会在同一时间停掉太多Pod(从而导致服务中断或者服务降级等)。 PodDisruptionBudget资源对象用于指定一个Pod集合在一段时间内存活的最小实例数量或者百分比。一个 PodDisruptionBudget 作用于一组被同一个控制器管理的 Pod,例如 DeploymentReplicaSet 或RC。与通常的Pod删除不同,驱逐Pod的控制器将使用 /eviction 接口对Pod进行驱逐,如果这一主动驱逐行为违反了 PodDisruptionBudget的约定,就会被API Server拒绝。kubectl drain 操作将遵循 PodDisruptionBudget的设定,如果在该节点上运行了属于同一服务的多个Pod,则为了保证最少存活数量,系统将确保每终止一个Pod,就一定会在另一台健康的Node上启动新的Pod,再继续终止下一个Pod。需要注意的是,Disruption Controller 不能取代 Deployment、Statefulset等具备副本控制能力的Controller。PodDisruptionBudget对象的保护作用仅仅针对主动驱逐场景,而非所有场景,比如针对下面这些场景,PodDisruptionBudget机制完全无效。 · 后端节点物理机的硬件发生故障。 · 集群管理员错误地删除虚拟机(实例)。 · 云提供商或管理程序发生故障,使虚拟机消失。 · 内核恐慌(kernel panic)。 · 节点由于集群网络分区而从集群中消失。 · 由于节点资源不足而将容器逐出。 对PodDisruptionBudget的定义包括如下几个关键参数。 · Label Selector:用于筛选被管理的Pod。 · minAvailable:指定驱逐过程中需要保障的最少Pod数量。minAvailable可以是一个数字,也可以是一个百分比,例如100%就表示不允许进行主动驱逐。 · maxUnavailable:要保证最大不可用的Pod数量或者比例。 · minAvailable 和 maxUnavailable 不能被同时定义。 除了Pod对象,PodDisruptionBudget目前也支持了具备扩容能力的CRD对象,即这些CRD拥有Scale子对象资源并支持扩容功能。 PodDisruptionBudget应用示例如下。 (1)创建一个Deployment,设置Pod副本数量为3: --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: name: nginx spec: replicas: 3 selector: matchLabels: name: nginx template: metadata: labels: name: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 protocol: TCP (2)接下来创建一个PodDisruptionBudget 资源对象: --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: nginx spec: minAvailable: 3 selector: matchLabels: name: nginx PodDisruptionBudget 使用的是和 Deployment 一样的 Label Selector,并且设置存活Pod的数量不得少于3个。 (3)主动驱逐验证。 对Pod的主动驱逐操作将通过驱逐API(/eviction)来完成。可以将这个API看作受策略控制的对Pod的DELETE操作。要实现一次主动驱逐(更准确的说法是创建一个Eviction资源),则需要POST一个JSON请求,以 eviction.json 文件格式表示,例如希望驱逐名为“nginx-1968750913-0k01k”的Pod,内容如下: { "apiVersion": "policy/vlbeta1", "kind": "Eviction", "metadata":{ "name": "nginx-1968750913-0k01k", "namespace": "default" } } 用curl命令执行驱逐操作: $ curl -v -H 'Content-type: application/json' http://<k8s_master>/api/v1/namespaces/default/pods/nginx-1968750913-0k01k/eviction -d @eviction.json 由于 PodDisruptionBudget 设置存活的Pod数量不能少于3个,因此驱逐操作会失败,使用 kubectl get pods命令查看Pod列表,会看到Pod的数量和名称都没有发生变化。 (4)删除 PodDisruptionBudget资源对象,再次验证驱逐Pod.用 kubectl delete pdb nginx 命令删除 PodDisruptionBudget资源对象: $ kubectl delete -f pdb.yaml 再次执行上文中的curl指令,会执行成功,通过 kubectl get pods 命令查看Pod列表,会发现Pod的数量虽然没有发生变化,但是指定的Pod已被删除,取而代之的是一个新的Pod。
Kubernetes 的早期版本依靠 Heapster 来实现完整的性能数据采集和监控功能,Kubernetes从1.8版本开始,性能数据开始以 Metrics API方式提供标准化接口,并且从1.10版本开始将 Heapster 替换为 Metrics Server。在Kubernetes新的监控体系中,Metrics Server用于提供核心指标(Core Metrics),包括Node、Pod的CPU和内存使用指标。对其他自定义指标(Custom Metrics)的监控则由 Prometheus 等组件来完成。
Metrics Server 在部署完成后,将通过 Kubernetes 核心 API Server 的 /apis/metrics.k8s.io/vlbetal 路径提供Node和Pod的监控数据。Metrics Server 源代码和部署配置可以在 Kubernete 官方 GitHub代码库中找到。 Metrics Server提供的数据既可以用于基于CPU和内存的自动水平扩缩容(HPA)功能,也可以用于自动垂直扩缩容(VPA)功能,VPA相关的内容请参考12.3节的说明。 Metrics Server的YAML配置主要包括以下内容。 (1)Deployment和Service的定义及相关RBAC策略: --- apiVersion: apps/v1 kind: Deployment metadata: name: metrics-server namespace: kube-system labels: k8s-app: metrics-server spec: selector: matchLabels: k8s-app: metrics-server template: metadata: name: metrics-server labels: k8s-app: metrics-server spec: serviceAccountName: metrics-server volumes: - name: tmp-dir emptyDir: {} containers: - name: metrics-server image: k8s.gcr.io/metrics-server/metrics-server:v0.3.7 imagePullPolicy: IfNotPresent args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-insecure-tls - --kubelet-preferred-address-types=InternalIP ports: - name: main-port containerPort: 4443 protocol: TCP securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 volumeMounts: - name: tmp-dir mountPath: /tmp nodeSelector: kubernetes.io/os: linux kubernetes.io/arch: "amd64" --- apiVersion: v1 kind: Service metadata: name: metrics-server namespace: kube-system labels: kubernetes.io/name: "Metrics-server" kubernetes.io/cluster-service: "true" spec: selector: k8s-app: metrics-server ports: - port: 443 protocol: TCP targetPort: main-port --- apiVersion: v1 kind: ServiceAccount metadata: name: metrics-server namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:metrics-server rules: - apiGroups: - "" resources: - pods - nodes - nodes/stats - namespaces - configmaps verbs: - get - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:metrics-server roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:metrics-server subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system (2) APIService 资源及相关 RBAC 策略: --- apiVersion: apiregistration.k8s.io/v1beta1 kind: APIService metadata: name: v1beta1.metrics.k8s.io spec: service: name: metrics-server namespace: kube-system group: metrics.k8s.io version: v1beta1 insecureSkipTLSVerify: true groupPriorityMinimum: 100 versionPriority: 100 --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:aggregated-metrics-reader labels: rbac.authorization.k8s.io/aggregate-to-view: "true" rbac.authorization.k8s.io/aggregate-to-edit: "true" rbac.authorization.k8s.io/aggregate-to-admin: "true" rules: - apiGroups: ["metrics.k8s.io"] resources: ["pods", "nodes"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: metrics-server:system:auth-delegator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: metrics-server-auth-reader namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: extension-apiserver-authentication-reader subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system 通过 kubectl create 命令创建 metrics-server 服务: $ kubectl cteate -f metrics-server.yaml 确认 metrics-server 的 Pod 启动成功后,就可以使用 kubectl top nods 和 kubectl top pods 命令监控 Node 和 Pod 的 CPU、内存资源的使用情况了。
日志对于业务分析和系统分析是非常重要的数据。在一个Kubernetes集群中,大量容器应用运行在众多 Node 上,各容器和Node的系统组件都会生成许多日志文件。但是容器具有不稳定性,在发生故障时可能被 Kubernetes 重新调度,Node也可能由于故障无法使用,造成日志丢失,这就要求管理员对容器和系统组件生成的日志进行统一规划和管理。
容器应用和系统组件输出日志的场景如下。 `1.容器应用输出日志的场景` 容器应用可以选择将日志输出到不同的目标位置: · 输出到标准输出和标准错误输出; · 输出到某个日志文件; · 输出到某个外部系统。 输出到标准输出和标准错误输出的日志通常由容器引擎接管,并保存在容器运行的Node上,例如 Docker会被保存在 /var/lib/docker/containers 目录下。在Kubernetes中,用户可以通过 kubectl logs 命令查看容器输出到stdout和stderr的日志,例如: $ kubectl logs demo-app 输出到文件中的日志,其保存位置依赖于容器应用使用的存储类型。如果未指定特别的存储,则容器内应用程序生成的日志文件由容器引擎(例如Docker)进行管理(例如存储为本地文件),在容器退出时可能被删除。需要将日志持久化存储时,容器可以选择使用 Kubernetes 提供的某种存储卷(Volume),例如 hostpath(保存在Node上)、nfs(保存在NFS服务器上)、PVC(保存在某种网络共享存储上)。保存在共享存储卷中的日志要求容器应用确保文件名或子目录名称不冲突。 某些容器应用也可能将日志直接输出到某个外部系统中,例如通过一个消息队列(如Kafka)转发到一个后端日志存储中心。在这种情况下,外部系统的搭建方式和应用程序如何将日志输出到外部系统,应由容器应用程序的运维人员负责,不应由Kubernetes负责。 `2.系统组件输入日志的场景` Kubernetes 的系统组件主要包括在 Master 上运行的管理组件(kube-apiserver、kube-controller-manager 和kube-scheduler),以及在每个Node上运行的管理组件(kubelet和kube-proxy)。这些系统组件生成的日志对于 Kubernetes 集群的正常运行和故障排查都非常重要。 系统组件的日志可以通过 --log-dir 参数保存到指定的目录下(例如/var/log),或者通过 --logtostderr 参数输出到标准错误输出中(stderr)。如果系统管理员将这些服务配置为 systemd 的系统服务,日志则会被 journald 系统保存。 Kubernetes 从1.19版本开始,开始引入对结构化日志的支持,使日志格式统一,便于日志中字段的提取、保存和后续处理。结构化日志以JSON格式保存。目前 kube-apiserver、kube-controller-manager、kube-scheduler 和 kubelet 这4个服务都支持通过启动参数 --logging-format=json 设置 JSON 格式的日志,需要注意的是,JSON格式的日志在启用 systemd 的系统中将被保存到 journald 中,在未使用systemd的系统中将在 /var/log 目录下生成 *.log 文件,不能再通过 --log-dir 参数指定保存目录。 例如,查看 kube-controller-manager 服务的JSON格式日志: $ journalctl -b -u kube-controller-manager.service 其中一行JSON日志的内容如下: { "ts": 1600226772320.624, "msg": " Controller will reconcile labels. \n", "v": 0 } Kubernetes应用程序在生成JSON格式日志时,可以设置的字段如下。 · ts:UNIX格式的浮点数类型的时间戳(必填项)。 · v:日志级别,默认为0(必填项)。 · msg:日志信息(必填项)。 · err:错误信息,字符串类型(可选项)。 `3.审计日志` Kubernetes的审计日志可通过 kube-apiserver 服务的 --audit-log-* 相关参数进行设置,关于审计日志的详细说明请参考10.9节的说明。 对于以上各种日志输出的情况,管理员应该对日志进行以下管理。 (1)对于输出到主机(Node)上的日志,管理员可以考虑在每个Node上都启动一个日志采集工具,将日志采集后汇总到统一日志中心,以供日志查询和分析,具体做法如下。 · 对于容器输出到stdout和stderr的日志:管理员应该配置容器引擎(例如Docker)对日志的轮转(rotate)策略,以免文件无限增长,将主机磁盘空间耗尽。 · 对于系统组件输出到主机目录上(如/var/log)的日志,管理员应该配置各系统组件日志的轮转(rotate)策略,以免文件无限增长等将主机磁盘空间耗尽。 · 对于容器应用使用hostpath输出到Node上的日志:管理员应合理分配主机目录,在满足容器应用存储空间需求的同时,可以考虑使用采集工具将日志采集并汇总到统一的日志中心,并定时清理Node的磁盘空间。 (2)对于输出到容器内的日志,容器应用可以将日志直接输出到容器环境内的某个目录下,这可以减轻应用程序在共享存储中管理不同文件名或子目录的复杂度。在这种情况下,管理员可以为应用容器提供一个日志采集的 sidecar 容器,对容器的日志进行采集,并将其汇总到某个日志中心,供业务运维人员查询和分析。 在Kubernetes生态中,推荐采用 Fluentd+Elasticsearch+Kibana 完成对系统组件和容器日志的采集、汇总和查询的统一管理机制。下面对系统的部署和应用进行说明。
在本节的示例中,我们先对 Node 上的各种日志进行采集和汇总。Fluentd+Elasticsearch+Kibana系统的逻辑关系架构如图10.14所示。
这里假设将 Kubernetes 系统组件的日志输出到 /var/log目录下,容器输出到 stdout和stderr 的日志由 Docker Server 保存在 /var/lib/docker/containers 目录下。我们通过在每个Node上都部署一个 Fluentd 容器来采集本节点在这两个目录下的日志文件,然后将其汇总到 Elasticsearch 库中保存,用户通过 Kibana 提供的Web页面查询日志。部署过程主要包括3个组件:Elasticsearch、Fluentd和Kibana。
对于容器应用输出到容器目录下的日志,可以为业务应用容器配置一个日志采集 sidecar 容器,对业务容器生成的日志进行采集并汇总到某个日志中心,供业务运维人员查询和分析,这通常用于业务日志的采集和汇总。后端的日志存储可以使用 Elasticsearch,也可以使用其他类型的数据库(如MongoDB),或者通过消息队列进行转发(如Kafka),需要根据业务应用的具体需求进行合理选择。 日志采集 sidecar 工具也有多种选择,常见的开源软件包括 Fluentd、Filebeat、Flume等,在下例中使用 Fluentd 进行说明。 为业务应用容器配置日志采集 sidecar 时,需要在Pod中定义两个容器,然后创建一个共享的Volume供业务应用容器生成日志文件,并供日志采集 sidecar 读取日志文件。例如: --- apiVersion: v1 kind: Pod metadata: name: webapp spec: containers: - name: webapp image: kubeguide/tomcat-app:v1 ports: - containerPort: 8080 volumeMounts: - name: app-logs mountPath: /usr/local/tomcat/logs # log collector sidecar - name: fluentd image: fluent/fluentd:v1.9.2-1.0 volumeMounts: - name: app-logs mountPath: /app-logs - name: config-volume mountPath: /etc/fluent/config.d volumes: - name: app-logs emptyDir: {} - name: config-volume configMap: name: fluentd-config 在这个Pod 中创建了一个类型为 emptyDir 的 Volume,挂载到 webapp 容器的/usr/local/tomcat/logs目录下,也挂载到 fluentd 容器中的 /app-logs 目录下。Volume的类型不限于 emptyDir ,需要根据业务需求合理选择。 在Pod创建成功之后,webapp 容器会在 /usr/local/tomcat/logs 目录下持续生成日志文件,fluentd 容器作为 sidecar 持续采集应用程序的日志文件,并将其保存到后端的日志库中。需要注意的是,webapp 容器应负责日志文件的清理工作,以免耗尽磁盘空间。
Kubernetes 的 Web UI网页管理工具是 kubernetes-dashboard,可提供部署应用、资源对象管理、容器日志查询、系统监控等常用的集群管理功能。为了在页面上显示系统资源的使用情况,需要部署 Metrics Server,部署方式详见10.7.1节的说明。
我们可以使用官方GitHub仓库提供的YAML文件一键部署 kubernetes-dashboard。该配置文件的内容如下,其中包含 kubernetes-dashboard所需的RBAC、Deployment和Service等资源的定义:
#百度
在 Kubernetes Dashboard 中可以查看集群中应用的运行状态,也能够创建和修改各种 Kubernetes 资源,比如 Deployment、Job、DaemonSet 等。用户可以 Scale Up/Down Deployment、执行 Rolling Update、重启某个 Pod 或者通过向导部署新的应用。Dashboard 能显示集群中各种资源的状态以及日志信息。
可以说,Kubernetes Dashboard 提供了 kubectl 的绝大部分功能,大家可以根据情况进行选择。
随着容器技术和微服务架构逐渐被企业接受,在Kubernetes上已经能便捷地部署简单的应用了。但对于复杂的应用或者中间件系统,在Kubernetes上进行容器化部署并非易事,通常需要研究 Docker 镜像的运行需求、环境变量等内容,为容器配置依赖的存储、网络等资源,并设计和编写 Deployment、ConfigMap、Service、Volume、Ingress 等 YAML 文件,再将其依次提交给 Kubernetes 部署。总之,微服务架构和容器化给复杂应用的部署和管理都带来了很大的挑战。
Helm由Deis公司(已被微软收购)发起,用于对需要在Kubernetes上部署的复杂应用进行定义、安装和更新,是CNCF基金会的毕业项目,由 Helm 社区维护。Helm 将Kubernetes的资源如Deployment、Service、ConfigMap、Ingress等,打包到一个 Chart(图表)中,而 Chart 被保存到Chart仓库,由Chart仓库存储、分发和共享。Helm支持应用Chart的版本管理,简化了Kubernetes 应用部署的应用定义、打包、部署、更新、删除和回滚等操作。
简单来说,Helm通过将各种Kubernetes资源打包,类似于Linux的 apt-get 或 yum 工具,来完成复杂软件的安装和部署,并且支持部署实例的版本管理等,大大简化了在Kubernetes上部署和管理应用的复杂度。
Helm主要包括以下组件。
· Chart:Helm软件包,包含一个应用所需资源对象的YAML文件,通常以 .tgz 压缩包形式提供,也可以是文件夹形式。
· Repository(仓库):用于存放和共享Chart的仓库。
· Config(配置数据):部署时设置到Chart中的配置数据。
· Release:基于Chart和Config部署到Kubernetes集群中运行的一个实例。一个Chart可以被部署多次,每次的Release都不相同。
基于Helm的工作流程如下。
(1)开发人员将开发好的Chart上传到Chart仓库。
(2)运维人员基于Chart的定义,设置必要的配置数据(Config),使用Helm命令行工具将应用一键部署到Kubernetes集群中,以Release概念管理后续的更新、回滚等。
(3)Chart仓库中的Chart可以用于共享和分发。
本章将对 Kubernetes集群中常见问题的排查方法进行说明。为了跟踪和发现在Kubernetes集群中运行的容器应用出现的问题,我们常用如下查错方法。
(1)查看Kubernetes对象的当前运行时信息,特别是与对象关联的`Event事件`。这些事件记录了相关主题、发生时间、最近发生时间、发生次数及事件原因等,对排查故障非常有价值。此外,通过查看对象的运行时数据,我们还可以发现参数错误、关联错误、状态异常等明显问题。由于在Kubernetes中多种对象相互关联,因此这一步可能会涉及多个相关对象的排查问题。
(2)对于服务、容器方面的问题,可能需要深入容器内部进行故障诊断,此时可以通过查看容器的`运行日志`来定位具体问题。
(3)对于某些复杂问题,例如Pod调度这种全局性的问题,可能需要结合集群中每个节点上的`Kubernetes服务日志`来排查。比如搜集 Master上的 kube-apiserver、kube-schedule、kube-controler-manager服务日志,以及各个Node上的kubelet、kube-proxy服务日志,通过综合判断各种信息,就能找到问题的成因并解决问题。
在Kubernetes集群中创建Pod后,我们可以通过kubectl get pods命令查看Pod列表,但通过该命令显示的信息有限。Kubernetes提供了 kubectl describe pod 命令来查看一个Pod的详细信息,例如:
`$ kubectl describe pod redis-master-bobr0Redis-master-bobr0`
通过 kubectl describe pod 命令,可以显示Pod创建时的配置定义、状态等信息,还可以显示与该 Pod 相关的最近的Event(事件),事件信息对于查错非常有用。如果某个 Pod一直处于Pending 状态,我们就可以通过kubectl describe 命令了解具体原因。例如,从 Event 事件中获知Pod失败的原因可能有以下几种。
· 没有可用的Node以供调度。
· 开启了资源配额管理,但在当前调度的目标节点上资源不足。
· 镜像下载失败。
通过 kubectl describe 命令,我们还可以查看其他 Kubernetes对象,包括Node、RC、Service、Namespace、Secrets等,对每种对象都会显示相关的其他信息。
例如,查看一个服务的详细信息:
`$ kubectl describe service redis-master`
如果要查看的对象属于某个特定的命名空间,就需要加上 --namespace=<namespace> 进行查询。例如:
`$ kubectl get service kube-dns --namespace=kube-system`
在需要排查容器内部应用程序生成的日志时,我们可以使用 kubectl logs <pod_name> 命令:
`$ kubectl logs redis-master-bobr0`
如果在某个Pod中包含多个容器,就需要通过 -c 参数指定容器的名称来查看,例如:
`$ kubectl logs <pod_name> -c <container_name> `
其效果与在Pod的宿主机上运行 docker logs <container_id>一样。
容器中应用程序生成的日志与容器的生命周期是一致的,所以在容器被销毁之后,容器内部的文件也会被丢弃,包括日志等。如果需要保留容器内应用程序生成的日志,则可以使用挂载的 Volume 将容器内应用程序生成的日志保存到宿主机上,还可以通过一些工具如 Fluentd、Elasticsearch 等对日志进行采集。
如果在Linux系统上安装 Kubernetes,并且使用systemd系统管理 Kubernetes 服务,那么 systemd 的 journal 系统会接管服务程序的输出日志。在这种环境中,可以通过使用 systemd status 或 journalctl 工具来查看系统服务的日志。 例如,使用 systemctl status 命令查看 kube-controller-manager 服务的日志: # systemctl status kube-controller-manager -l 使用journalctl命令查看: # journalctl -u kube-controller-manager 如果不使用 systemd 系统接管 Kubernetes 服务的标准输出,则也可以通过日志相关的启动参数来指定日志的存放目录。 · --logtostderr=false:不输出到stderr。 · --log-dir=/var/log/kubernetes:日志的存放目录。 · --alsologtostderr=false:将其设置为true时,表示将日志同时输出到文件和stderr。 · --v=0:glog的日志级别。 · --vmodule=gfs*=2,test*=4:glog基于模块的详细日志级别。 在--log_dir设置的目录下可以查看各服务进程生成的日志文件,日志文件的数量和大小依赖于日志级别的设置。例如,kube-controller-manager可能生成的几个日志文件如下: · kube-controller-manager.ERROR; · kube-controller-manager.INFO; · kube-controller-manager.WARNING; · kube-controller-manager.kubernetes-master.unknownuser.log.ERROR.20200930-O173939.9847; · kube-controller-manager.kubernetes-master.unknownuser.log.INFO.20200930-173939.9847; · kube-controller-manager.kubernetes-master.unknownuser.log. WARNING.20200930-173939.9847。 在大多数情况下,我们从WARNING和ERROR级别的日志中就能找到问题的成因,但有时还需要排查INFO级别的日志甚至DEBUG级别的详细日志。此外,etcd服务也属于Kubernetes集群的重要组成部分,所以不能忽略它的日志。 `如果某个 Kubernetes 对象存在问题,则可以用这个对象的名字作为关键字搜索Kubernetes的日志来发现和解决问题。在大多数情况下,我们遇到的主要是与Pod对象相关的问题,比如无法创建Pod、Pod启动后就停止或者Pod副本无法增加,等等。此时,可以先确定Pod在哪个节点上,然后登录这个节点,从kubelet的日志中查询该Pod的完整日志,然后进行问题排查。对于与Pod扩容相关或者与RC相关的问题,则很可能在kube-controller-manager 及 kube-scheduler的日志中找出问题的关键点。`` `另外,kube-proxy 经常被我们忽视,因为即使它意外停止,Pod的状态也是正常的,但会导致某些服务访问异常。这些错误通常与每个节点上的kube-proxy 服务有着密切的关系。遇到这些问题时,首先要排查kube-proxy服务的日志,同时排查防火墙服务,要特别留意在防火墙中是否有人为添加的可疑规则。`
本节对Kubernetes系统中的一些常见问题及解决方法进行说明。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。