10 笔记

00 Kubernetes简介 阅读更多

0.1. Kubernetes资源对象 0.1.1. Pods 0.1.2. Services 0.1.3. Replication Controllers 0.1.3.1. Replication Controller主要有如下用法 0.1.4. Labels 0.2. Kuberbetes构件 0.2.1. Master 0.2.1.1. 下图是Master的工作流程 0.2.1.2. Master主要构件及工作流 0.2.2. Kubelet 0.2.3. Proxy 0.3. Kubernetes 详细架构 0.3.1. Kubernetes主要由以下几个核心组件组成 0.3.2. 除了核心组件,还有一些推荐的Add-ons 0.3.3. 分层架构 Kubernetes是Google开源的容器集群管理系统,其提供应用部署、维护、 扩展机制等功能,利用Kubernetes能方便地管理跨机器运行容器化的应用,其主要功能如下: 使用Docker对应用程序包装(package)、实例化(instantiate)、运行(run) 以集群的方式运行、管理跨机器的容器 解决Docker跨机器容器之间的通讯问题 K- ubernetes的自我修复机制使得容器集群总是运行在用户期望的状态 当前Kubernetes支持GCE、vShpere、CoreOS、OpenShift、Azure等平台,除此之外,也可以直接运行在物理机上。 0.1. Kubernetes资源对象 0.1.1. Pods Pod是Kubernetes的基本操作单元,把相关的一个或多个容器构成一个Pod,通常Pod里的容器运行相同的应用。==Pod包含的容器运行在同一个Minion(Host)上==,看作一个统一管理单元,共享相同的volumes和network namespace/IP和Port空间。 0.1.2. Services Services也是Kubernetes的基本操作单元,是真实应用服务的抽象,每一个服务后面都有很多对应的容器来支持,++通过Proxy的port和服务selector决定服务请求传递给后端提供服务的容器++,对外表现为一个单一访问接口,外部不需要了解后端如何运行,这给扩展或维护后端带来很大的好处。 0.1.3. Replication Controllers Replication Controller确保任何时候Kubernetes集群中有指定数量的pod副本(replicas)在运行, 如果少于指定数量的pod副本(replicas),Replication Controller会启动新的Container,反之会杀死多余的以保证数量不变。 Replication Controller使用预先定义的pod模板创建pods,一旦创建成功,pod 模板和创建的pods没有任何关联,可以修改pod 模板而不会对已创建pods有任何影响,也可以直接更新通过Replication Controller创建的pods。对于利用pod 模板创建的pods,Replication Controller根据label selector来关联,通过修改pods的label可以删除对应的pods。 0.1.3.1. Replication Controller主要有如下用法 Rescheduling:Replication Controller会确保Kubernetes集群中指定的pod副本(replicas)在运行, 即使在节点出错时。 Scaling:通过修改Replication Controller的副本(replicas)数量来水平扩展或者缩小运行的pods。 Rolling updates:Replication Controller的设计原则使得可以一个一个地替换pods来rolling updates服务。 Multiple release tracks:如果需要在系统中运行multiple release的服务,Replication Controller使用labels来区分multiple release tracks。 0.1.4. Labels Labels是用于区分Pod、Service、Replication Controller的key/value键值对,Pod、Service、 Replication Controller可以有多个label,但是每个label的key只能对应一个value。 Labels是Service和Replication Controller运行的基础,为了将访问Service的请求转发给后端提供服务的多个容器,正是通过标识容器的labels来选择正确的容器。 同样,Replication Controller也使用labels来管理通过pod 模板创建的一组容器,这样Replication Controller可以更加容易,方便地管理多个容器,无论有多少容器。 0.2. Kuberbetes构件 Kubenetes整体框架如下图,主要包括kubecfg、Master API Server、Kubelet、Minion(Host)以及Proxy。 0.2.1. Master Master定义了Kubernetes 集群Master/API Server的主要声明,包括Pod Registry、Controller Registry、Service Registry、Endpoint Registry、Minion Registry、Binding Registry、RESTStorage以及Client, 是client(Kubecfg)调用Kubernetes API,管理Kubernetes主要构件Pods、Services、Minions、容器的入口。 Master由API Server、Scheduler以及Registry等组成。 0.2.1.1. 下图是Master的工作流程 Kubecfg将特定的请求,比如创建Pod,发送给Kubernetes Client。 Kubernetes Client将请求发送给API server。 API Server根据请求的类型,比如创建Pod时storage类型是pods,然后依此选择何种REST Storage API对请求作出处理。 REST Storage API对请求作相应的处理。 将处理的结果存入高可用键值存储系统Etcd中。 在API Server响应Kubecfg的请求后,Scheduler会根据Kubernetes Client获取集群中运行Pod及Minion信息。 依据从Kubernetes Client获取的信息,Scheduler将未分发的Pod分发到可用的Minion节点上。 0.2.1.2. Master主要构件及工作流 Minion Registry:负责跟踪Kubernetes 集群中有多少Minion(Host)。Kubernetes封装Minion Registry成实现Kubernetes API Server的RESTful API接口REST,通过这些API,我们可以对Minion Registry做Create、Get、List、Delete操作,由于Minon只能被创建或删除,所以不支持Update操作,并把Minion的相关配置信息存储到etcd。除此之外,Scheduler算法根据Minion的资源容量来确定是否将新建Pod分发到该Minion节点。 Pod Registry:负责跟踪Kubernetes集群中有多少Pod在运行,以及这些Pod跟Minion是如何的映射关系。将Pod Registry和Cloud Provider信息及其他相关信息封装成实现Kubernetes API Server的RESTful API接口REST。通过这些API,我们可以对Pod进行Create、Get、List、Update、Delete操作,并将Pod的信息存储到etcd中,而且可以通过Watch接口监视Pod的变化情况,比如一个Pod被新建、删除或者更新。 Service Registry:负责跟踪Kubernetes集群中运行的所有服务。根据提供的Cloud Provider及Minion Registry信息把Service Registry封装成实现Kubernetes API Server需要的RESTful API接口REST。利用这些接口,我们可以对Service进行Create、Get、List、Update、Delete操作,以及监视Service变化情况的watch操作,并把Service信息存储到etcd。 Controller Registry:负责跟踪Kubernetes集群中所有的Replication Controller,Replication Controller维护着指定数量的pod 副本(replicas)拷贝,如果其中的一个容器死掉,Replication Controller会自动启动一个新的容器,如果死掉的容器恢复,其会杀死多出的容器以保证指定的拷贝不变。通过封装Controller Registry为实现Kubernetes API Server的RESTful API接口REST, 利用这些接口,我们可以对Replication Controller进行Create、Get、List、Update、Delete操作,以及监视Replication Controller变化情况的watch操作,并把Replication Controller信息存储到etcd。 Endpoints Registry:负责收集Service的endpoint,比如Name:"mysql",Endpoints:["10.10.1.1:1909","10.10.2.2:8834"],同Pod Registry,Controller Registry也实现了Kubernetes API Server的RESTful API接口,可以做Create、Get、List、Update、Delete以及watch操作。 Bindig Registry:包括一个需要绑定Pod的ID和Pod被绑定的Host,Scheduler写Binding Registry后,需绑定的Pod被绑定到一个host。Binding Registry也实现了Kubernetes API Server的RESTful API接口,但Binding Registry是一个write-only对象,所有只有Create操作可以使用, 否则会引起错误。 Scheduler:收集和分析当前Kubernetes集群中所有Minion节点的资源(内存、CPU)负载情况,然后依此分发新建的Pod到Kubernetes集群中可用的节点。由于一旦Minion节点的资源被分配给Pod,那这些资源就不能再分配给其他Pod, 除非这些Pod被删除或者退出, 因此,Kubernetes需要分析集群中所有Minion的资源使用情况,保证分发的工作负载不会超出当前该Minion节点的可用资源范围。具体来说,Scheduler做以下工作: 实时监测Kubernetes集群中未分发的Pod。 实时监测Kubernetes集群中所有运行的Pod,Scheduler需要根据这些Pod的资源状况安全地将未分发的Pod分发到指定的Minion节点上。 Scheduler也监测Minion节点信息,由于会频繁查找Minion节点,Scheduler会缓存一份最新的信息在本地。 最后,Scheduler在分发Pod到指定的Minion节点后,会把Pod相关的信息Binding写回API Server。 0.2.2. Kubelet 详细结构图: 根据上图可知Kubelet是Kubernetes集群中每个Minion和Master API Server的连接点,Kubelet运行在每个Minion上,是Master API Server和Minion之间的桥梁,接收Master API Server分配给它的commands和work,与持久性键值存储etcd、file、server和http进行交互,读取配置信息。 Kubelet的主要工作是管理Pod和容器的生命周期,其包括Docker Client、Root Directory、Pod Workers、Etcd Client、Cadvisor Client以及Health Checker组件,具体工作如下: 通过Worker给Pod异步运行特定的Action。 设置容器的环境变量。 给容器绑定Volume。 给容器绑定Port。 根据指定的Pod运行一个单一容器。 杀死容器。 给指定的Pod创建network 容器。 删除Pod的所有容器。 同步Pod的状态。 从Cadvisor获取container info、 pod info、root info、machine info。 检测Pod的容器健康状态信息。 在容器中运行命令。 0.2.3. Proxy Proxy是为了解决外部网络能够访问跨机器集群中容器提供的应用服务而设计的,从上图可知Proxy服务也运行在每个Minion上。Proxy提供TCP/UDP sockets的proxy,每创建一种Service,Proxy主要从etcd获取Services和Endpoints的配置信息,或者也可以从file获取,然后根据配置信息在Minion上启动一个Proxy的进程并监听相应的服务端口,当外部请求发生时,Proxy会根据Load Balancer将请求分发到后端正确的容器处理。 0.3. Kubernetes 详细架构 0.3.1. Kubernetes主要由以下几个核心组件组成 etcd保存了整个集群的状态; apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制; controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等; scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上; kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理; Container runtime负责镜像管理以及Pod和容器的真正运行(CRI); kube-proxy负责为Service提供cluster内部的服务发现和负载均衡; 0.3.2. 除了核心组件,还有一些推荐的Add-ons kube-dns负责为整个集群提供DNS服务 Ingress Controller为服务提供外网入口 Heapster提供资源监控 Dashboard提供GUI Federation提供跨可用区的集群 Fluentd-elasticsearch提供集群日志采集、存储与查询 0.3.3. 分层架构 Kubernetes设计理念和功能其实就是一个类似Linux的分层架构,如下图所示 核心层:Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境 应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS解析等) 管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等) 接口层:kubectl命令行工具、客户端SDK以及集群联邦 生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴 Kubernetes外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS应用、ChatOps等 Kubernetes内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等

01 Kubernetes安装 阅读更多

0.1. 配置防火墙 0.2. 配置Selinux 0.3. 安装Kubernetes集群 0.4. kubeadm安装集群 0.4.1. 配置yum源 0.4.2. 下载docker的yum源 0.4.3. 安装docker 0.4.4. 使用官方安装脚本自动安装 0.4.5. 手动安装 0.4.6. 安装校验 0.4.7. 下载kubernetes的yum源 0.4.8. 安装kubernetes 0.4.9. 启动服务 0.4.10. 修改docker镜像地址 0.5. 运行kubeadm init 安装Master 0.5.1. 安装前的准备工作 0.5.2. 如果使用其他容器Runtime 0.5.3. 查看配置文件 0.6. 安装Harbor私有仓库 0.6.1. docker 配置私有源 0.6.2. 配置其他接待的hosts文件,使得访问私有源的地址都指向Harbor 0.6.3. harbor 启动私有镜像库 0.6.3.1. 安装Master 0.6.4. 部署容器网络插件 0.6.5. 通过 Taint/Toleration 调整 Master 执行 Pod 的策略 0.6.5.1. Pod 声明 Toleration 0.6.6. 部署Dashboard可视化插件 0.6.7. 部署容器存储插件 0.1. 配置防火墙 Kubernetes的Master与Node之间有大量的网络通信,安全的做法是在防火墙上配置各组件需要相互通信的端口号,在一个安全的内网环境中可以关闭防护墙。 systemctl stop firewalld.service systemctl disable firewalld.service 0.2. 配置Selinux 禁用主机上的Selinux,使得容器能够读取主机文件系统。 setenforce 0 //临时关闭Selinux getenforce //查看当前Selinux的安全策略 sestatus //查看当前Selinux的状态 修改Selinux的配置文件,可以永久关闭 vim /etc/sysconfig/selinux SELINUX=disabled //enforcing为开启状态 sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config 对Selinux配置完成后,需要重启服务器。 0.3. 安装Kubernetes集群 在Centos系统上,最简单的安装方式是通过YUM安装工具进行安装,即执行如下命令: yum install kubernetes 以上安装方式存在明显的缺点,安装完成后需要对整个集群进行配置,整个过程复杂,且容易出错。因此采用kubeadm工具进行安装。 0.4. kubeadm安装集群 0.4.1. 配置yum源 国内可以从阿里云的镜像仓库中下载相关的yum源 0.4.2. 下载docker的yum源 在阿里云镜像仓库找到docker的yum源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 0.4.3. 安装docker 安装依赖组件: yum install -y yum-utils device-mapper-persistent-data lvm2 0.4.4. 使用官方安装脚本自动安装 curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 0.4.5. 手动安装 //step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 // Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo //Step 3: 更新并安装 Docker-CE sudo yum makecache fast sudo yum -y install docker-ce //Step 4: 开启Docker服务 sudo service docker start 注意: 官方软件源默认启用了最新的软件,可以通过编辑软件源的方式获取各个版本的软件包。例如官方并没有将测试版本的软件源置为可用,可以通过以下方式开启。同理可以开启各种测试版本等。 vim /etc/yum.repos.d/docker-ce.repo //将 [docker-ce-test] 下方的 enabled=0 修改为 enabled=1 安装指定版本的Docker-CE: // Step 1: 查找Docker-CE的版本: yum list docker-ce.x86_64 --showduplicates | sort -r Loading mirror speeds from cached hostfile Loaded plugins: branch, fastestmirror, langpacks docker-ce.x86_64 17.03.1.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 17.03.1.ce-1.el7.centos @docker-ce-stable docker-ce.x86_64 17.03.0.ce-1.el7.centos docker-ce-stable Available Packages // Step2 : 安装指定版本的Docker-CE: (VERSION 例如上面的 17.03.0.ce.1-1.el7.centos) sudo yum -y install docker-ce-[VERSION] 0.4.6. 安装校验 $ docker version Client: Version: 17.03.0-ce API version: 1.26 Go version: go1.7.5 Git commit: 3a232c8 Built: Tue Feb 28 07:52:04 2017 OS/Arch: linux/amd64 Server: Version: 17.03.0-ce API version: 1.26 (minimum version 1.12) Go version: go1.7.5 Git commit: 3a232c8 Built: Tue Feb 28 07:52:04 2017 OS/Arch: linux/amd64 Experimental: false 0.4.7. 下载kubernetes的yum源 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF 0.4.8. 安装kubernetes yum install -y kubelet kubeadm kubectl 0.4.9. 启动服务 systemctl enable docker && systemctl start docker systemctl enable kubelet && systemctl start kubelet 0.4.10. 修改docker镜像地址 默认情况下是从gcr.io进行下载的,从国外下载可能失败或者很慢,所以将镜像地址修改为国内的镜像,修改docker 的配置文件 /etc/docker/daemon.json {"registry-mirrors": ["https://gn6g9no6.mirror.aliyuncs.com"]} 或者 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://gn6g9no6.mirror.aliyuncs.com"] } EOF 以上加速地址为阿里云的加速地址,登录阿里云docker hub 首页 https://dev.aliyun.com/search.html ,点击管理中心,找到镜像加速器,复制加速器地址。 重启docker服务 sudo systemctl daemon-reload sudo systemctl restart docker 0.5. 运行kubeadm init 安装Master 0.5.1. 安装前的准备工作 RHEL / CentOS 7存在由于iptables被绕过而导致流量路由不正确的问题。应确保在sysctl配置中将net.bridge.bridge-nf-call-iptables设置为1,例如: cat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sysctl --system modprobe br_netfilter sysctl -p /etc/sysctl.d/k8s.conf kubernetes1.8开始需要关闭swap: swapoff -a 修改 /etc/fstab 文件,注释掉 SWAP 的自动挂载,使用free -m确认swap已经关闭 wappiness参数调整,修改/etc/sysctl.d/k8s.conf添加下面一行: vm.swappiness=0 sysctl -p /etc/sysctl.d/k8s.conf //使参数生效 也可以在kubelet的启动文件中添加参数来关闭swap vim /etc/sysconfig/kubelet KUBELET_EXTRA_ARGS=--fail-swap-on=false 0.5.2. 如果使用其他容器Runtime 使用Docker时,kubeadm会自动检测kubelet的cgroup驱动程序,并在运行时将其设置在/var/lib/kubelet/kubeadm-flags.env文件中。 如果您使用的是其他CRI,则必须使用cgroup-driver值修改文件/etc/default/kubelet,如下所示: KUBELET_KUBEADM_EXTRA_ARGS= - cgroup-driver=<value> kubeadm init和kubeadm join将使用此文件为kubelet提供额外的用户定义参数 请注意,如果您的CRI的cgroup驱动程序不是cgroupfs,您只需要这样做,因为这已经是kubelet中的默认值。 需要重新启动kubelet: systemctl daemon-reload systemctl restart kubelet 0.5.3. 查看配置文件 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf,内容如下: # Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/sysconfig/kubelet ExecStart= ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS 上面显示kubeadm部署的kubelet的配置文件–config=/var/lib/kubelet/config.yaml,实际去查看/var/lib/kubelet和这个config.yaml的配置文件都没有被创建。 可以猜想肯定是运行kubeadm初始化集群时会自动生成这个配置文件,而如果我们不关闭Swap的话,第一次初始化集群肯定会失败的。 0.6. 安装Harbor私有仓库 参考Harbor安装 0.6.1. docker 配置私有源 cd /etc/docker echo -e '{\n"insecure-registries":["k8s.gcr.io", "gcr.io", "quay.io"]\n}' > /etc/docker/daemon.json systemctl restart docker 0.6.2. 配置其他接待的hosts文件,使得访问私有源的地址都指向Harbor HARBOR_HOST="安装Host的IP地址" echo """$HARBOR_HOST gcr.io harbor.io k8s.gcr.io quay.io """ >> /etc/hosts 0.6.3. harbor 启动私有镜像库 docker load -i /path/to/k8s-repo-1.11.0 docker run --restart=always -d -p 80:5000 --name repo harbor.io:1180/system/k8s-repo:v1.11.0 查看镜像库中的镜像: docker run -it harbor.io:1180/system/k8s-repo:v1.11.0 list k8s.gcr.io/kube-apiserver-amd64:v1.11.0 k8s.gcr.io/kube-scheduler-amd64:v1.11.0 k8s.gcr.io/kube-controller-manager-amd64:v1.11.0 k8s.gcr.io/kube-proxy-amd64:v1.11.0 k8s.gcr.io/coredns:1.1.3 k8s.gcr.io/etcd-amd64:3.2.18 k8s.gcr.io/pause-amd64:3.1 k8s.gcr.io/pause:3.1 quay.io/calico/node:v3.1.3 quay.io/calico/cni:v3.1.3 k8s.gcr.io/heapster-influxdb-amd64:v1.3.3 k8s.gcr.io/heapster-grafana-amd64:v4.4.3 k8s.gcr.io/heapster-amd64:v1.4.2 k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3 k8s.gcr.io/traefik:1.6.5 k8s.gcr.io/prometheus:v2.3.1 0.6.3.1. 安装Master kubeadm init --apiserver-advertise-address 192.168.1.111 --pod-network-cidr 10.244.0.0/16 --kubernetes-version=1.6.0 参数说明 --apiserver-advertise-address:指明Master使用哪个interface与Cluster其他节点通信(不指定会使用带有默认网关的interface) --pod-network-cidr:指定pod网络范围,不同网络方案对该参数要求不同,这里使用flannel,必须设置为这个CIDR --kubernetes-version=1.6.0 :指定需要安装的版本 执行的时候如果报错,可以强制忽略错误: 添加如下参数 --ignore-preflight-errors=Swap [init] using Kubernetes version: v1.12.0 [preflight] running pre-flight checks [preflight] Some fatal errors occurred: [ERROR Swap]: running with swap on is not supported. Please disable swap [preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...` 安装成功显示如下: Your Kubernetes master has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ You can now join any number of machines by running the following on each node as root: kubeadm join 192.168.50.2:6443 --token afh07i.hxfrpdy2v84u53qq --discovery-token-ca-cert-hash sha256:d2ab827fdd91bc33039a73433bfad6fe22628df65fd9328b6e8a495634f4d463 0.6.4. 部署容器网络插件 kubectl apply -f https://git.io/weave-kube-1.6 刚刚部署的 Weave 网络插件则在 kube-system 下面新建了一个Pod,一般来说,这些 Pod 就是容器网络插件在每个节点上的控制组件。 Kubernetes 支持容器网络插件,使用的是一个名叫CNI的通用接口,它也是当前容器网络的事实标准,市面上的所有容器网络开源项目都可以通过 CNI 接入Kubernetes,比如Flannel、Calico、Canal、Romana等等,它们的部署方式也都是类似的“一键部署”。关于这些开源项目的实现细节和差异, 在默认情况下,Kubernetes 的 Master 节点是不能运行用户 Pod 的,所以还需要额外做一个小操作: 0.6.5. 通过 Taint/Toleration 调整 Master 执行 Pod 的策略 默认情况下 Master 节点是不允许运行用户 Pod 的。而 Kubernetes 做到这一点,依靠的是 Kubernetes 的 Taint/Toleration 机制。 原理:一旦某个节点被加上了一个 Taint,即被“打上了污点”,那么所有 Pod 就都不能在这个节点上运行,因为 Kubernetes 的 Pod 都有“洁癖”。除非,有个别的 Pod 声明自己能“容忍”这个“污点”,即声明了 Toleration,它才可以在这个节点上运行。 其中,为节点打上“污点”(Taint)的命令是: kubectl taint nodes node1 foo=bar:NoSchedule 这时,该 node1 节点上就会增加一个键值对格式的Taint,即:foo=bar:NoSchedule。其中值里面的 NoSchedule,意味着这个 Taint 只会在调度新Pod时产生作用,而不会影响已经在 node1上运行的 Pod,哪怕它们没有 Toleration。 0.6.5.1. Pod 声明 Toleration 在 Pod 的.yaml 文件中的 spec 部分,加入 tolerations 字段即可: apiVersion: v1 kind: Pod ... spec: tolerations: - key: "foo" operator: "Equal" value: "bar" effect: "NoSchedule" 这个 Toleration 的含义是,这个 Pod 能“容忍”所有键值对为 foo=bar 的 Taint( operator:“Equal”,“等于”操作)。 通过 kubectl describe 检查一下 Master 节点的Taint 字段: $ kubectl describe node master Name: master Roles: master Taints: node-role.kubernetes.io/master:NoSchedule Master节点默认被加上了 node-role.kubernetes.io/master:NoSchedule 这样一个“污点”,其中“键”是 node-role.kubernetes.io/master ,而没有提供“值”。 此时,用“Exists”操作符(operator: “Exists”,“存在”即可)来说明,该Pod能够容忍所有以 foo 为键的 Taint,才能让这个 Pod 运行在该 Master 节点上: apiVersion: v1 kind: Pod ... spec: tolerations: - key: "foo" operator: "Exists" effect: "NoSchedule" 删除这个 Taint : kubectl taint nodes --all node-role.kubernetes.io/master- 如上所示,我们在“node-role.kubernetes.io/master”这个键后面加上了一个短横线“-”,这个格式就意味着移除所有以“node-role.kubernetes.io/master”为键的Taint。 0.6.6. 部署Dashboard可视化插件 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml 需要注意的是,由于 Dashboard 是一个 Web Server,很多人经常会在自己的公有云上无意地暴露 Dashboard 的端口,从而造成安全隐患。所以,1.7 版本之后的 Dashboard 项目部署完成后,默认只能通过 Proxy 的方式在本地访问。具体的操作,你可以查看 Dashboard 项目的官方文档。 修改dashboard的yaml文件,通过端口访问: kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kube-system spec: # 添加Service的type为NodePort type: NodePort ports: - port: 443 targetPort: 8443 # 添加映射到虚拟机的端口,k8s只支持30000以上的端口 nodePort: 30001 selector: k8s-app: kubernetes-dashboard 0.6.7. 部署容器存储插件 需要用数据卷(Volume)把外面宿主机上的目录或者文件挂载进容器的 Mount Namespace 中, 从而达到容器和宿主机共享这些目录或者文件的目的。容器里的应用,也就可以在这些数据卷中新建和写入文件。 可是,如果在某一台机器上启动的一个容器,显然无法看到其他机器上的容器在它们的数据卷里写入的文件。这是容器最典型的特征之一:无状态。 而容器的持久化存储,就是用来保存容器存储状态的重要手段:存储插件会在容器里挂载一个基于网络或者其他机制的远程数据卷,使得在容器里创建的文件,实际上是保存在远程存储服务器上,或者以分布式的方式保存在多个节点上,而与当前宿主机没有任何绑定关系。这样,无论你在其他哪个宿主机上启动新的容器,都可以请求挂载指定的持久化存储卷,从而访问到数据卷里保存的内容。这就是“持久化”的含义。 由于 Kubernetes 本身的松耦合设计,绝大多数存储项目,比如 Ceph、GlusterFS、NFS 等,都可以为 Kubernetes提供持久化存储能力。在这次的部署实战中,我会选择部署一个很重要的Kubernetes 存储插件项目:Rook。 Rook 项目是一个基于 Ceph 的 Kubernetes存储插件(它后期也在加入对更多存储实现的支持)。不过,不同于对 Ceph 的简单封装,Rook 在自己的实现中加入了水平扩展、迁移、灾难备份、监控 等大量的企业级功能,使得这个项目变成了一个完整的、生产级别可用的容器存储插件。 得益于容器化技术,用两条指令,Rook 就可以把复杂的 Ceph 存储后端部署起来: kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/cluster/examples/kubernetes/ceph/operator.yaml kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/cluster/examples/kubernetes/ceph/cluster.yaml 一个基于 Rook 持久化存储集群就以容器的方式运行起来了,而接下来在 Kubernetes 项目上创建的所有 Pod 就能够通过 Persistent Volume(PV)和 Persistent Volume Claim(PVC)的方式,在容器里挂载由 Ceph 提供的数据卷了。 而 Rook 项目,则会负责这些数据卷的生命周期管理、灾难备份等运维工作。

02 kubectl命令 阅读更多

0.1. 语法规则 0.2. 基础命令(初级) 0.3. 基础命令(中级) 0.4. 部署命令 0.5. 集群管理命令 0.6. 检修和调试命令 0.7. 高级命令 0.8. 设置命令 0.9. 其他命令 0.1. 语法规则 kubectl [command] [TYPE] [NAME] [flags] command:指定要在一个或多个资源上执行的操作,例如create,get,describe,delete。 TYPE:指定资源类型。资源类型区分大小写,可以指定单数,复数或缩写形式。 例如,以下命令产生相同的输出: kubectl get pod pod1 kubectl get pods pod1 kubectl get po pod1 NAME:指定资源的名称。==名称区分大小写==。如果省略名称,将显示所有资源的详细信息,例如 kubectl get pod 在多个资源上执行操作时,可以按类型和名称指定每个资源,或指定一个或多个文件: 按类型和名称指定资源:属于同一类型的资源划分在一个组,TYPE1 name1 name2 name<#>。例如: kubectl get pod example-pod1 example-pod2 分别指定多种资源,TYPE1/name1 TYPE1/name2 TYPE2/name3 TYPE<#>/name<#>。例如: kubectl get pod/example-pod1 replicationcontroller/example-rc1 使用一个或多个文件指定资源,-f file1 -f file2 -f file<#>,(使用YAML而不是JSON,因为YAML往往更加用户友好,特别是对于配置文件),例如: kubectl get pod -f ./pod.yaml flags:指定可选标志。例如,您可以使用-s或--server标志来指定Kubernetes API服务器的地址和端口。 重要提示:从命令行指定的标志将覆盖默认值和任何相应的环境变量。 0.2. 基础命令(初级) 命令 描述 create 通过文件名或stdin创建一个资源 expose 采取副本控制器,服务,部署或pod,并将其作为新的Kubernetes服务公开 run 在集群上运行特定映像 set 设置对象上的特定功能 0.3. 基础命令(中级) 命令 描述 get 显示一个或多个资源 explain 资源记录 edit 在服务器上编辑资源 delete 通过文件名,stdin,资源和名称,或资源和标签选择器删除资源 0.4. 部署命令 命令 描述 rollout 管理Deployment rollout rolling-update 执行给定副本控制器的滚动更新 scale 为Deployment,ReplicaSet,副本控制器或作业设置新的大小 scale | 自动为Deployment, ReplicaSet, or ReplicationController设置新的大小 0.5. 集群管理命令 命令 描述 certificate 修改证书资源 cluster-info 显示群集信息 top 显示资源(CPU /内存/存储)使用情况 cordon 将节点标记为不可用 uncordon 将节点标记为可用 drain 设定节点进入维护模式 taint 更新一个或多个节点上的阴影 0.6. 检修和调试命令 命令 描述 describe 显示特定资源或资源组的详细信息 logs 打印一个pod中一个容器的日志 attach 附加到正在运行的容器 exec 在容器中执行命令 port-forward 将一个或多个本地端口转发到pod proxy 运行代理到Kubernetes API服务器 cp 将文件和目录复制到容器中 0.7. 高级命令 命令 描述 apply 通过文件名或stdin将配置应用于资源 patch 使用策略合并补丁更新资源的字段 replace 用文件名或stdin替换一个资源 convert 在不同API版本之间转换配置文件 0.8. 设置命令 命令 描述 label 更新资源上的标签 annotate 更新资源上的注释 completion 输出shell完成代码到指定的shell中(如bash或zsh) 0.9. 其他命令 命令 描述 api-versions 在服务器上打印受支持的API版本,形式为“group / version” config 修改kubeconfig文件 help 帮助 version 打印客户端和服务器版本信息

03 pod异常 阅读更多

0.1. 常用命令行工具 0.2. Pod 一直处于 Pending 状态 0.3. Pod 处于 Waiting 或 ContainerCreating 状态 0.4. Pod 处于 ImagePullBackOff 状态 0.4.1. 私有镜像 0.5. Pod 一直处于 CrashLoopBackOff 状态 0.6. Pod 处于 Error 状态 0.7. Pod 处于 Terminating 或 Unknown 状态 0.8. Pod 行为异常 0.9. 修改静态 Pod 的 Manifest 后未自动重建 0.1. 常用命令行工具 工具 作用 例子 kubectl 用于查看 Kubernetes 集群以及容器的状态 如 kubectl describe pod <pod-name> journalctl 用于查看 Kubernetes 组件日志 如 journalctl -u kubelet -l iptables和ebtables 用于排查 Service 是否工作 如 iptables -t nat -nL 查看 kube-proxy 配置的 iptables 规则是否正常 tcpdump 用于排查容器网络问题 如 tcpdump -nn host 10.240.0.8 perf Linux 内核自带的性能分析工具 常用来排查性能问题 一般来说,无论 Pod 处于什么异常状态,都可以执行以下命令来查看 Pod 的状态: //查看 Pod 的配置是否正确 kubectl get pod <pod-name> -o yaml //查看 Pod 的事件 kubectl describe pod <pod-name> //查看容器日志 kubectl logs <pod-name> [-c <container-name>] 这些事件和日志通常都会有助于排查 Pod 发生的问题。 0.2. Pod 一直处于 Pending 状态 Pending 说明 Pod 还没有调度到某个 Node 上面。 可以通过 kubectl describe pod <pod-name> 命令查看到当前 Pod 的事件,进而判断为什么没有调度。 可能的原因: 资源不足,集群内所有的 Node 都不满足该 Pod 请求的 CPU、内存、GPU 等资源 HostPort 已被占用,通常推荐使用 Service 对外开放服务端口 0.3. Pod 处于 Waiting 或 ContainerCreating 状态 首先还是通过 kubectl describe pod <pod-name> 命令查看到当前 Pod 的事件。 可能的原因: 镜像拉取失败,比如配置了错误的镜像 Kubelet 无法访问镜像(国内环境访问 gcr.io 需要特殊处理) 私有镜像的密钥配置错误 镜像太大,拉取超时(可以适当调整 kubelet 的 --image-pull-progress-deadline 和 --runtime-request-timeout 选项) CNI 网络错误,一般需要检查 CNI 网络插件的配置,比如 无法配置 Pod 网络 无法分配 IP 地址 容器无法启动,需要检查是否打包了正确的镜像或者是否配置了正确的容器参数 0.4. Pod 处于 ImagePullBackOff 状态 这通常是镜像名称配置错误或者私有镜像的密钥配置错误导致。 这种情况可以使用 docker pull <image> 来验证镜像是否可以正常拉取。 0.4.1. 私有镜像 首先创建一个 docker-registry 类型的 Secret kubectl create secret docker-registry my-secret \ --docker-server=DOCKER_REGISTRY_SERVER \ --docker-username=DOCKER_USER \ --docker-password=DOCKER_PASSWORD \ --docker-email=DOCKER_EMAIL 然后在容器中引用这个 Secret ```bash spec: containers: name: private-reg-container image: imagePullSecrets: name: my-secret ``` 0.5. Pod 一直处于 CrashLoopBackOff 状态 CrashLoopBackOff 状态说明容器曾经启动了,但又异常退出了。 此时可以先查看一下容器的日志 kubectl logs <pod-name> kubectl logs --previous <pod-name> 这里可以发现一些容器退出的原因,比如: 容器进程退出 健康检查失败退出 此时如果还未发现线索,还可以到容器内执行命令来进一步查看退出原因 kubectl exec cassandra -- cat /var/log/cassandra/system.log 如果还是没有线索,那就需要 SSH 登录该 Pod 所在的Node上,查看 Kubelet 或者 Docker 的日志进一步排查了 查询 Node kubectl get pod <pod-name> -o wide 0.6. Pod 处于 Error 状态 通常处于 Error 状态说明 Pod 启动过程中发生了错误。 常见的原因: 依赖的 ConfigMap、Secret 或者 PV 等不存在 请求的资源超过了管理员设置的限制,比如超过了 LimitRange 等 违反集群的安全策略,比如违反了 PodSecurityPolicy 等 容器无权操作集群内的资源,比如开启 RBAC 后,需要为 ServiceAccount配置角色绑定 0.7. Pod 处于 Terminating 或 Unknown 状态 从 v1.5 开始,Kubernetes 不会因为 Node 失联而删除其上正在运行的 Pod,而是将其标记为 Terminating 或 Unknown 状态。 想要删除这些状态的 Pod 有三种方法: (1)从集群中删除该 Node。 使用公有云时,kube-controller-manager 会在 VM 删除后自动删除对应的 Node。 而在物理机部署的集群中,需要管理员手动删除 Node(如 kubectl delete node <node-name>。 (2)Node 恢复正常。Kubelet 会重新跟 kube-apiserver 通信确认这些 Pod 的期待状态,进而再决定删除或者继续运行这些 Pod。 (3)用户强制删除。用户可以执行 kubectl delete pods <pod> --grace-period=0 --force 强制删除 Pod。除非明确知道 Pod 的确处于停止状态(比如 Node 所在 VM 或物理机已经关机),否则不建议使用该方法。特别是 StatefulSet 管理的 Pod,强制删除容易导致脑裂或者数据丢失等问题。 0.8. Pod 行为异常 这里所说的行为异常是指 Pod没有按预期的行为执行。 比如没有运行 podSpec 里面设置的命令行参数。这一般是 podSpec yaml 文件内容有误。 可以尝试使用 --validate 参数重建容器 kubectl delete pod mypod kubectl create --validate -f mypod.yaml 也可以查看创建后的 podSpec 是否正确 kubectl get pod mypod -o yaml 0.9. 修改静态 Pod 的 Manifest 后未自动重建 Kubelet 使用 inotify 机制检测 /etc/kubernetes/manifests 目录(可通过 Kubelet 的 --pod-manifest-path 选项指定)中静态 Pod 的变化,并在文件发生变化后重新创建相应的 Pod。 但有时也会发生修改静态 Pod 的 Manifest 后未自动创建新 Pod 的情景,此时一个简单的修复方法是重启 Kubelet。

04 Pod部署失败 阅读更多

0.1. 错误的容器镜像/非法的仓库权限 0.2. 应用启动之后又挂掉 0.3. 缺失 ConfigMap 或者 Secret 0.3.1. 缺失 ConfigMap 0.3.2. 缺失 Secrets 0.4. 活跃度/就绪状态探测失败 0.5. 超出CPU/内存的限制 0.6. 资源配额 0.7. 集群资源不足 0.8. 持久化卷挂载失败 0.9. 校验错误 0.10. 容器镜像没有更新 0.11. 总结 0.1. 错误的容器镜像/非法的仓库权限 其中两个最普遍的问题是: (a)指定了错误的容器镜像, (b)使用私有镜像却不提供仓库认证信息。 这在首次使用 Kubernetes 或者绑定 CI/CD 环境时尤其棘手。 让我们看个例子。 首先我们创建一个名为 fail 的 deployment,它指向一个不存在的 Docker 镜像: kubectl run fail --image=rosskukulinski/dne:v1.0.0 然后我们查看 Pods,可以看到有一个状态为 ErrImagePull 或者 ImagePullBackOff 的 Pod: $ kubectl get pods NAME READY STATUS RESTARTS AGE fail-1036623984-hxoas 0/1 ImagePullBackOff 0 2m 想查看更多信息,可以 describe 这个失败的 Pod: kubectl describe pod fail-1036623984-hxoas 查看 describe 命令的输出中 Events 这部分: Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 5m 5m 1 {default-scheduler } Normal Scheduled Successfully assigned fail-1036623984-hxoas to gke-nrhk-1-default-pool-a101b974-wfp7 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal Pulling pulling image "rosskukulinski/dne:v1.0.0" 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Warning Failed Failed to pull image "rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dne not found 5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "fail" with ErrImagePull: "Error: image rosskukulinski/dne not found" 5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal BackOff Back-off pulling image "rosskukulinski/dne:v1.0.0" 5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "fail" with ImagePullBackOff: "Back-off pulling image \"rosskukulinski/dne:v1.0.0\"" 显示错误的那句话:Failed to pull image "rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dne not found 告诉我们 Kubernetes无法找到镜像 rosskukulinski/dne:v1.0.0。 因此问题变成:为什么 Kubernetes 拉不下来镜像? 除了 网络连接问题 外,还有三个主要元凶: 镜像 tag 不正确 镜像不存在(或者是在另一个仓库) Kubernetes 没有权限去拉那个镜像 如果你没有注意到你的镜像 tag 的拼写错误,那么最好就用你本地机器测试一下。 通常我会在本地开发机上,用 docker pull 命令,带上 完全相同的镜像 tag,来跑一下。 比如上面的情况,我会运行命令 docker pull rosskukulinski/dne:v1.0.0。 如果这成功了,那么很可能 Kubernetes 没有权限 去拉取这个镜像。参考镜像拉取 Secrets 来解决这个问题。 如果失败了,那么我会继续用不显式带 tag 的镜像测试 - docker pull rosskukulinski/dne - 这会尝试拉取 tag 为 latest 的镜像。如果这样成功,表明原来 指定的 tag 不存在 。这可能是人为原因,拼写错误,或者 CI/CD 的配置错误。 如果 docker pull rosskukulinski/dne(不指定 tag)也失败了,那么我们碰到了一个更大的问题:我们所有的镜像仓库中都没有这个镜像。 默认情况下,Kubernetes 使用 Dockerhub 镜像仓库,如果你在使用 Quay.io,AWS ECR,或者 Google Container Registry,你要在镜像地址中指定这个仓库的 URL,比如使用 Quay,镜像地址就变成 quay.io/rosskukulinski/dne:v1.0.0。 如果你在使用 Dockerhub,那你应该再次确认你发布镜像到 Dockerhub 的系统,确保名字和 tag 匹配你的 deployment 正在使用的镜像。 注意:观察 Pod 状态的时候,镜像缺失和仓库权限不正确是没法区分的。其它情况下,Kubernetes 将报告一个 ErrImagePull 状态。 0.2. 应用启动之后又挂掉 无论你是在 Kubernetes 上启动新应用,还是迁移应用到已存在的平台,应用在启动之后就挂掉都是一个比较常见的现象。 我们创建一个 deployment,它的应用会在1秒后挂掉: kubectl run crasher --image=rosskukulinski/crashing-app 我们看一下 Pods 的状态: $ kubectl get pods NAME READY STATUS RESTARTS AGE crasher-2443551393-vuehs 0/1 CrashLoopBackOff 2 54s CrashLoopBackOff 告诉我们,Kubernetes 正在尽力启动这个 Pod,但是一个或多个容器已经挂了,或者正被删除。 让我们 describe 这个 Pod 去获取更多信息: $ kubectl describe pod crasher-2443551393-vuehs Name: crasher-2443551393-vuehs Namespace: fail Node: gke-nrhk-1-default-pool-a101b974-wfp7/10.142.0.2 Start Time: Fri, 10 Feb 2017 14:20:29 -0500 Labels: pod-template-hash=2443551393 run=crasher Status: Running IP: 10.0.0.74 Controllers: ReplicaSet/crasher-2443551393 Containers: crasher: Container ID: docker://51c940ab32016e6d6b5ed28075357661fef3282cb3569117b0f815a199d01c60 Image: rosskukulinski/crashing-app Image ID: docker://sha256:cf7452191b34d7797a07403d47a1ccf5254741d4bb356577b8a5de40864653a5 Port: State: Terminated Reason: Error Exit Code: 1 Started: Fri, 10 Feb 2017 14:22:24 -0500 Finished: Fri, 10 Feb 2017 14:22:26 -0500 Last State: Terminated Reason: Error Exit Code: 1 Started: Fri, 10 Feb 2017 14:21:39 -0500 Finished: Fri, 10 Feb 2017 14:21:40 -0500 Ready: False Restart Count: 4 好可怕,Kubernetes 告诉我们这个 Pod 正被 Terminated,因为容器里的应用挂了。我们还可以看到应用的 Exit Code 是 1。后面我们可能还会看到一个 OOMKilled 错误。 我们的应用正在挂掉?为什么? 首先我们查看应用日志。假定你发送应用日志到 stdout(事实上你也应该这么做),你可以使用 kubectl logs 看到应用日志: kubectl logs crasher-2443551393-vuehs 不幸的是,这个 Pod 没有任何日志。这可能是因为我们正在查看一个新起的应用实例,因此我们应该查看前一个容器: kubectl logs crasher-2443551393-vuehs --previous 什么!我们的应用仍然不给我们任何东西。这个时候我们应该给应用加点启动日志了,以帮助我们定位这个问题。我们也可以本地运行一下这个容器,以确定是否缺失环境变量或者挂载卷。 0.3. 缺失 ConfigMap 或者 Secret Kubernetes 最佳实践建议**通过 ConfigMaps 或者 Secrets 传递应用的 运行时配置 **。这些数据可以包含数据库认证信息,API endpoints,或者其它配置信息。 一个常见的错误是,创建的 deployment 中引用的 ConfigMaps 或者 Secrets 的属性不存在,有时候甚至引用的 ConfigMaps 或者 Secrets 本身就不存在。 0.3.1. 缺失 ConfigMap 第一个例子,我们将尝试创建一个 Pod,它加载 ConfigMap 数据作为环境变量: # configmap-pod.yaml apiVersion: v1 kind: Pod metadata: name: configmap-pod spec: containers: - name: test-container image: gcr.io/google_containers/busybox command: [ "/bin/sh", "-c", "env" ] env: - name: SPECIAL_LEVEL_KEY valueFrom: configMapKeyRef: name: special-config key: special.how 让我们创建一个 Pod: kubectl create -f configmap-pod.yaml 在等待几分钟之后,我们可以查看我们的 Pod: $ kubectl get pods NAME READY STATUS RESTARTS AGE configmap-pod 0/1 RunContainerError 0 3s Pod 状态是 RunContainerError 。我们可以使用 kubectl describe 了解更多: $ kubectl describe pod configmap-pod [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 20s 20s 1 {default-scheduler } Normal Scheduled Successfully assigned configmap-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 19s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulling pulling image "gcr.io/google_containers/busybox" 18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulled Successfully pulled image "gcr.io/google_containers/busybox" 18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "test-container" with RunContainerError: "GenerateRunContainerOptions: configmaps \"special-config\" not found" Events 章节的最后一条告诉我们什么地方错了。Pod 尝试访问名为 special-config 的 ConfigMap,但是在该 namespace 下找不到。一旦我们创建这个 ConfigMap,Pod 应该重启并能成功拉取运行时数据。 在 Pod 规格说明中访问 Secrets作为环境变量会产生相似的错误,就像我们在这里看到的 ConfigMap错误一样。 通过 Volume 来访问 Secrets 或者 ConfigMap会发生什么呢? 0.3.2. 缺失 Secrets 下面是一个pod规格说明,它引用了名为 myothersecret 的 Secrets,并尝试把它挂为卷: # missing-secret.yaml apiVersion: v1 kind: Pod metadata: name: secret-pod spec: containers: - name: test-container image: gcr.io/google_containers/busybox command: [ "/bin/sh", "-c", "env" ] volumeMounts: - mountPath: /etc/secret/ name: myothersecret restartPolicy: Never volumes: - name: myothersecret secret: secretName: myothersecret 让我们用 kubectl create -f missing-secret.yaml 来创建一个 Pod。 几分钟后,我们 get Pods,可以看到 Pod 仍处于 ContainerCreating 状态: $ kubectl get pods NAME READY STATUS RESTARTS AGE secret-pod 0/1 ContainerCreating 0 4h 这就奇怪了。我们 describe 一下,看看到底发生了什么: $ kubectl describe pod secret-pod Name: secret-pod Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2 Start Time: Sat, 11 Feb 2017 14:07:13 -0500 Labels: Status: Pending IP: Controllers: [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 18s 18s 1 {default-scheduler } Normal Scheduled Successfully assigned secret-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 18s 2s 6 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedMount MountVolume.SetUp failed for volume "kubernetes.io/secret/337281e7-f065-11e6-bd01-42010af0012c-myothersecret" (spec.Name: "myothersecret") pod "337281e7-f065-11e6-bd01-42010af0012c" (UID: "337281e7-f065-11e6-bd01-42010af0012c") with: secrets "myothersecret" not found Events 章节再次解释了问题的原因。它告诉我们 Kubelet 无法从名为 myothersecret 的 Secret 挂卷。为了解决这个问题,我们可以创建 myothersecret ,它包含必要的安全认证信息。一旦 myothersecret 创建完成,容器也将正确启动。 0.4. 活跃度/就绪状态探测失败 在 Kubernetes 中处理容器问题时,开发者需要学习的重要一课是, 你的容器应用是 running 状态,不代表它在工作 。 Kubernetes 提供了两个基本特性,称作活跃度探测和就绪状态探测。本质上来说,活跃度/就绪状态探测将定期地执行一个操作(例如发送一个 HTTP 请求,打开一个 tcp 连接,或者在你的容器内运行一个命令),以确认你的应用和你预想的一样在工作。 如果活跃度探测失败,Kubernetes 将杀掉你的容器并重新创建一个。 如果就绪状态探测失败,这个 Pod 将不会作为一个服务的后端 endpoint,也就是说不会流量导到这个 Pod,直到它变成 Ready。 如果你试图部署变更你的活跃度/就绪状态探测失败的应用,滚动部署将一直悬挂,因为它将等待你的所有 Pod 都变成 Ready。 这个实际是怎样的情况? 以下是一个 Pod 规格说明,它定义了活跃度/就绪状态探测方法,都是基于8080端口对 /healthy 路由进行健康检查: apiVersion: v1 kind: Pod metadata: name: liveness-pod spec: containers: - name: test-container image: rosskukulinski/leaking-app livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 让我们创建这个 Pod:kubectl create -f liveness.yaml,过几分钟后查看发生了什么: $ kubectl get pods NAME READY STATUS RESTARTS AGE liveness-pod 0/1 Running 4 2m 2分钟以后,我们发现 Pod 仍然没处于 Ready 状态,并且它已被重启了4次。让我们 describe 一下查看更多信息: $ kubectl describe pod liveness-pod Name: liveness-pod Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2 Start Time: Sat, 11 Feb 2017 14:32:36 -0500 Labels: Status: Running IP: 10.108.88.40 Controllers: Containers: test-container: Container ID: docker://8fa6f99e6fda6e56221683249bae322ed864d686965dc44acffda6f7cf186c7b Image: rosskukulinski/leaking-app Image ID: docker://sha256:7bba8c34dad4ea155420f856cd8de37ba9026048bd81f3a25d222fd1d53da8b7 Port: State: Running Started: Sat, 11 Feb 2017 14:40:34 -0500 Last State: Terminated Reason: Error Exit Code: 137 Started: Sat, 11 Feb 2017 14:37:10 -0500 Finished: Sat, 11 Feb 2017 14:37:45 -0500 [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 8m 8m 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-pod to gke-ctm-1-sysdig2-35e99c16-tgfm 8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with docker id 0fb5f1a56ea0; Security:[seccomp=unconfined] 8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Started Started container with docker id 0fb5f1a56ea0 7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with docker id 3f2392e9ead9; Security:[seccomp=unconfined] 7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Killing Killing container with docker id 0fb5f1a56ea0: pod "liveness-pod_fail(d75469d8-f090-11e6-bd01-42010af0012c)" container "test-container" is unhealthy, it will be killed and re-created. 8m 16s 10 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Liveness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused 8m 1s 85 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Readiness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused Events 章节再次救了我们。我们可以看到活跃度探测和就绪状态探测都失败了。关键的一句话是 container "test-container" is unhealthy, it will be killed and re-created。这告诉我们 Kubernetes 正在杀这个容器,因为容器的活跃度探测失败了。 这里有三种可能性: 你的探测不正确,健康检查的 URL 是否改变了? 你的探测太敏感了, 你的应用是否要过一会才能启动或者响应? 你的应用永远不会对探测做出正确响应,你的数据库是否配置错了 查看 Pod 日志是一个开始调测的好地方。一旦你解决了这个问题,新的 deployment 应该就能成功了。 0.5. 超出CPU/内存的限制 Kubernetes 赋予集群管理员限制 Pod 和容器的 CPU 或内存数量的能力。作为应用开发者,你可能不清楚这个限制,导致 deployment 失败的时候一脸困惑。 我们试图部署一个未知 CPU/memory 请求限额的 deployment: # gateway.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gateway spec: template: metadata: labels: app: gateway spec: containers: - name: test-container image: nginx resources: requests: memory: 5Gi 你会看到我们设了 5Gi 的资源请求。让我们创建这个 deployment:kubectl create -f gateway.yaml。 现在我们可以看到我们的 Pod: $ kubectl get pods No resources found. 为啥,让我们用 describe 来观察一下我们的 deployment: $ kubectl describe deployment/gateway Name: gateway Namespace: fail CreationTimestamp: Sat, 11 Feb 2017 15:03:34 -0500 Labels: app=gateway Selector: app=gateway Replicas: 0 updated | 1 total | 0 available | 1 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 0 max unavailable, 1 max surge OldReplicaSets: NewReplicaSet: gateway-764140025 (0/1 replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 4m 4m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-764140025 to 1 基于最后一行,我们的 deployment 创建了一个 ReplicaSet(gateway-764140025) 并把它扩展到 1。这个是用来管理 Pod 生命周期的实体。我们可以 describe 这个 ReplicaSet: $ kubectl describe rs/gateway-764140025 Name: gateway-764140025 Namespace: fail Image(s): nginx Selector: app=gateway,pod-template-hash=764140025 Labels: app=gateway pod-template-hash=764140025 Replicas: 0 current / 1 desired Pods Status: 0 Running / 0 Waiting / 0 Succeeded / 0 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 6m 28s 15 {replicaset-controller } Warning FailedCreate Error creating: pods "gateway-764140025-" is forbidden: [maximum memory usage per Pod is 100Mi, but request is 5368709120., maximum memory usage per Container is 100Mi, but request is 5Gi.] 哈知道了。集群管理员设置了每个 Pod 的最大内存使用量为 100Mi(好一个小气鬼!)。你可以运行 kubectl describe limitrange 来查看当前租户的限制。 你现在有3个选择: 要求你的集群管理员提升限额 减少 deployment 的请求或者限额设置 直接编辑限额 0.6. 资源配额 和资源限额类似,Kubernetes 也允许管理员给每个 namespace 设置资源配额。这些配额可以在 Pods,Deployments,PersistentVolumes,CPU,内存等资源上设置软性或者硬性限制。 让我们看看超出资源配额后会发生什么。以下是我们的 deployment 例子: # test-quota.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gateway-quota spec: template: spec: containers: - name: test-container image: nginx 我们可用 kubectl create -f test-quota.yaml 创建,然后观察我们的 Pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1 Running 0 16s 看起来很好,现在让我们扩展到 3 个副本: kubectl scale deploy/gateway-quota --replicas=3 然后再次观察 Pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1 Running 0 9m 啊,我们的pod去哪了?让我们观察一下 deployment: $ kubectl describe deploy/gateway-quota Name: gateway-quota Namespace: fail CreationTimestamp: Sat, 11 Feb 2017 16:33:16 -0500 Labels: app=gateway Selector: app=gateway Replicas: 1 updated | 3 total | 1 available | 2 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge OldReplicaSets: NewReplicaSet: gateway-quota-551394438 (1/3 replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 9m 9m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-quota-551394438 to 1 5m 5m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-quota-551394438 to 3 在最后一行,我们可以看到 ReplicaSet 被告知扩展到 3 。我们用 describe 来观察一下这个 ReplicaSet 以了解更多信息: kubectl describe replicaset gateway-quota-551394438 Name: gateway-quota-551394438 Namespace: fail Image(s): nginx Selector: app=gateway,pod-template-hash=551394438 Labels: app=gateway pod-template-hash=551394438 Replicas: 1 current / 3 desired Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 11m 11m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: gateway-quota-551394438-pix5d 11m 30s 33 {replicaset-controller } Warning FailedCreate Error creating: pods "gateway-quota-551394438-" is forbidden: exceeded quota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1 哦!我们的 ReplicaSet 无法创建更多的 pods 了,因为配额限制了: exceeded quota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1。 和资源限额类似,我们也有 3 个选项: 要求集群管理员提升该 namespace 的配额 删除或者收缩该 namespace 下其它的 deployment 直接编辑配额 0.7. 集群资源不足 除非你的集群开通了集群自动伸缩功能,否则总有一天你的集群中 CPU 和内存资源会耗尽。 这不是说 CPU 和内存被完全使用了,而是指它们被 Kubernetes 调度器完全使用了。如同我们在第 5 点看到的,集群管理员可以限制开发者能够申请分配给 pod 或者容器的 CPU 或者内存的数量。聪明的管理员也会设置一个默认的 CPU/内存 申请数量,在开发者未提供申请额度时使用。 如果你所有的工作都在 default 这个 namespace 下工作,你很可能有个默认值 100m 的容器 CPU申请额度,对此你甚至可能都不清楚。运行 kubectl describe ns default 检查一下是否如此。 我们假定你的 Kubernetes 集群只有一个包含 CPU 的节点。你的 Kubernetes 集群有 1000m 的可调度 CPU。 当前忽略其它的系统 pods(kubectl -n kube-system get pods),你的单节点集群能部署 10 个 pod(每个 pod 都只有一个包含 100m 的容器)。 10 Pods (1 Container 100m) = 1000m Cluster CPUs 当你扩大到 11 个的时候,会发生什么? 下面是一个申请 1CPU(1000m)的 deployment 例子: # cpu-scale.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: cpu-scale spec: template: metadata: labels: app: cpu-scale spec: containers: - name: test-container image: nginx resources: requests: cpu: 1 我把这个应用部署到有 2 个可用 CPU 的集群。除了我的 cpu-scale 应用,Kubernetes 内部服务也在消耗 CPU 和内存。 我们可以用 kubectl create -f cpu-scale.yaml 部署这个应用,并观察 pods: $ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-xstti 1/1 Running 0 5m 第一个 pod 被调度并运行了。我们看看扩展一个会发生什么: $ kubectl scale deploy/cpu-scale --replicas=2 deployment "cpu-scale" scaled $ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-phb4j 0/1 Pending 0 4m cpu-scale-908056305-xstti 1/1 Running 0 5m 我们的第二个pod一直处于 Pending,被阻塞了。我们可以 describe 这第二个 pod 查看更多的信息: $ kubectl describe pod cpu-scale-908056305-phb4j Name: cpu-scale-908056305-phb4j Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 08:57:51 -0500 Labels: app=cpu-scale pod-template-hash=908056305 Status: Pending IP: Controllers: ReplicaSet/cpu-scale-908056305 [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 3m 3m 1 {default-scheduler } Warning FailedScheduling pod (cpu-scale-908056305-phb4j) failed to fit in any node fit failure on node (gke-ctm-1-sysdig2-35e99c16-wx0s): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-tgfm): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-qwds): Insufficient cpu 好吧,Events 模块告诉我们 Kubernetes 调度器(default-scheduler)无法调度这个 pod 因为它无法匹配任何节点。它甚至告诉我们每个节点哪个扩展点失败了(Insufficient cpu)。 那么我们如何解决这个问题?如果你太渴望你申请的 CPU/内存 的大小,你可以减少申请的大小并重新部署。当然,你也可以请求你的集群管理员扩展这个集群(因为很可能你不是唯一一个碰到这个问题的人)。 现在你可能会想:我们的 Kubernetes 节点是在我们的云提供商的自动伸缩群组里,为什么他们没有生效呢? 原因是,你的云提供商没有深入理解 Kubernetes 调度器是做啥的。利用 Kubernetes 的集群自动伸缩能力允许你的集群根据调度器的需求自动伸缩它自身。如果你在使用 GCE,集群伸缩能力是一个 beta 特性。 0.8. 持久化卷挂载失败 另一个常见错误是创建了一个引用不存在的持久化卷(PersistentVolumes)的 deployment。不论你是使用 PersistentVolumeClaims(你应该使用这个!),还是直接访问持久化磁盘,最终结果都是类似的。 下面是我们的测试 deployment,它想使用一个名为 my-data-disk 的 GCE 持久化卷: # volume-test.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: volume-test spec: template: metadata: labels: app: volume-test spec: containers: - name: test-container image: nginx volumeMounts: - mountPath: /test name: test-volume volumes: - name: test-volume # This GCE PD must already exist (oops!) gcePersistentDisk: pdName: my-data-disk fsType: ext4 让我们创建这个 deployment:kubectl create -f volume-test.yaml,过几分钟后查看 pod: kubectl get pods NAME READY STATUS RESTARTS AGE volume-test-3922807804-33nux 0/1 ContainerCreating 0 3m 3 分钟的等待容器创建时间是很长了。让我们用 describe 来查看这个 pod,看看到底发生了什么: $ kubectl describe pod volume-test-3922807804-33nux Name: volume-test-3922807804-33nux Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 09:24:50 -0500 Labels: app=volume-test pod-template-hash=3922807804 Status: Pending IP: Controllers: ReplicaSet/volume-test-3922807804 [...] Volumes: test-volume: Type: GCEPersistentDisk (a Persistent Disk resource in Google Compute Engine) PDName: my-data-disk FSType: ext4 Partition: 0 ReadOnly: false [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 4m 4m 1 {default-scheduler } Normal Scheduled Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedMount Unable to mount volumes for pod "volume-test-3922807804-33nux_fail(e2180d94-f12e-11e6-bd01-42010af0012c)": timeout expired waiting for volumes to attach/mount for pod "volume-test-3922807804-33nux"/"fail". list of unattached/unmounted volumes=[test-volume] 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedSync Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod "volume-test-3922807804-33nux"/"fail". list of unattached/unmounted volumes=[test-volume] 3m 50s 3 {controller-manager } Warning FailedMount Failed to attach volume "test-volume" on node "gke-ctm-1-sysdig2-35e99c16-qwds" with: GCE persistent disk not found: diskName="my-data-disk" zone="us-central1-a" 很神奇! Events 模块留有我们一直在寻找的线索。我们的 pod 被正确调度到了一个节点(Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds),但是那个节点上的 kubelet 无法挂载期望的卷 test-volume。那个卷本应该在持久化磁盘被关联到这个节点的时候就被创建了,但是,正如我们看到的,controller-manager 失败了:Failed to attach volume "test-volume" on node "gke-ctm-1-sysdig2-35e99c16-qwds" with: GCE persistent disk not found: diskName="my-data-disk" zone="us-central1-a"。 最后一条信息相当清楚了:为了解决这个问题,我们需要在 GKE 的 us-central1-a 区中创建一个名为 my-data-disk 的持久化卷。一旦这个磁盘创建完成,controller-manager 将挂载这块磁盘,并启动容器创建过程。 0.9. 校验错误 看着整个 build-test-deploy 任务到了 deploy 步骤却失败了,原因竟是 Kubernetes 对象不合法。还有什么比这更让人沮丧的! 你可能之前也碰到过这种错误: $ kubectl create -f test-application.deploy.yaml error: error validating "test-application.deploy.yaml": error validating data: found invalid field resources for v1.PodSpec; if you choose to ignore these errors, turn validation off with --validate=false 在这个例子中,我尝试创建以下 deployment: # test-application.deploy.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: test-app spec: template: metadata: labels: app: test-app spec: containers: - image: nginx name: nginx resources: limits: cpu: 100m memory: 200Mi requests: cpu: 100m memory: 100Mi 一眼望去,这个 YAML 文件是正确的,但错误消息会证明是有用的。错误说的是 found invalid field resources for v1.PodSpec,再仔细看一下 v1.PodSpec, 我们可以看到 resource 对象变成了 v1.PodSpec 的一个子对象。事实上它应该是 v1.Container 的子对象。在把 resource 对象缩进一层后,这个 deployment 对象就可以正常工作了。 除了查找缩进错误,另一个常见的错误是写错了对象名(比如 peristentVolumeClaim 写成了 persistentVolumeClaim)。这个错误曾经在我们时间很赶的时候绊住了我和另一位高级工程师。 为了能在早期就发现这些错误,我推荐在 pre-commit 钩子或者构建的测试阶段添加一些校验步骤。 例如,你可以用 python -c 'import yaml,sys;yaml.safe_load(sys.stdin)' < test-application.deployment.yaml 验证 YAML 格式 使用标识 --dry-run 来验证 Kubernetes API 对象,比如这样: kubectl create -f test-application.deploy.yaml --dry-run --validate=true 重要提醒:校验 Kubernetes 对象的机制是在服务端的校验,这意味着 kubectl 必须有一个在工作的 Kubernetes 集群与之通信。不幸的是,当前 kubectl 还没有客户端的校验选项,但是已经有 issue(kubernetes/kubernetes #29410 和 kubernetes/kubernetes #11488)在跟踪这个缺失的特性了。 0.10. 容器镜像没有更新 我了解的在使用 Kubernetes 的大多数人都碰到过这个问题,它也确实是一个难题。 这个场景就像下面这样: 使用一个镜像 tag(比如:rosskulinski/myapplication:v1) 创建一个 deployment 注意到 myapplication 镜像中存在一个 bug 构建了一个新的镜像,并推送到了相同的 tag(rosskukulinski/myapplication:v1) 删除了所有 myapplication 的 pods,新的实例被 deployment 创建出了 发现 bug 仍然存在 重复 3-5 步直到你抓狂为止 这个问题关系到 Kubernetes 在启动 pod 内的容器时是如何决策是否做 docker pull 动作的。 在 v1.Container 说明中,有一个选项 ImagePullPolicy: Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. 因为我们把我们的镜像 tag 标记为 :v1,默认的镜像拉取策略是 IfNotPresent。Kubelet 在本地已经有一份 rosskukulinski/myapplication:v1 的拷贝了,因此它就不会在做 docker pull 动作了。当新的 pod 出现的时候,它仍然使用了老的有问题的镜像。 有三个方法来解决这个问题: 切成 :latest tag(千万不要这么做!) deployment 中指定 ImagePullPolicy: Always 使用唯一的 tag(比如基于你的代码版本控制器的 commit id) 在开发阶段或者要快速验证原型的时候,我会指定 ImagePullPolicy: Always 这样我可以使用相同的 tag 来构建和推送。 然而,在我的产品部署阶段,我使用基于 Git SHA-1 的唯一 tag。这样很容易查到产品部署的应用使用的源代码。 0.11. 总结 哇哦,我们有这么多地方要当心。到目前为止,你应该已经成为一个能定位,识别和修复失败的 Kubernetes 部署的专家了。 一般来说,大部分常见的部署失败都可以用下面的命令定位出来: kubectl describe deployment/<deployname> kubectl describe replicaset/<rsname> kubectl get pods kubectl describe pod/<podname> kubectl logs <podname> --previous 在追求自动化,把我从繁琐的定位工作中解放出来的过程中,我写了一个 bash 脚本,它在 CI/CD 的部署过程中任何失败的时候,都可以跑。在 Jenkins/CircleCI 等的构建输出中,将显示有用的 Kubernetes 信息,帮助开发者快速找到任何明显的问题。

05 Kubernetes笔记 阅读更多

0.1. 概念 0.2. Kubernetes架构 0.2.1. Master 0.2.1.1. API Server(kube-apiserver) 0.2.1.2. Scheduler(kube-scheduler) 0.2.1.3. Controller Manager(kube-controller-manager) 0.2.2. etcd 0.2.3. Pod网络 0.2.4. Node 0.2.4.1. Kubelet 0.2.4.2. kube-proxy 0.2.5. Pod网络 0.3. 运行应用 0.3.1. Deployment 0.3.2. YAML配置文件格式 0.3.3. 用label控制Pod的位置 0.4. DaemonSet 0.4.1. Job 0.4.1.1. Job并行 0.4.2. 定时Job 0.5. Service访问pod 0.6. Cluster-IP 底层实现 0.7. DNS访问Service 0.8. 外网访问Service 0.9. 健康检查 0.9.1. 默认的健康检查 0.9.2. Liveness探测 0.10. Readiness探测 0.10.1. liveness与readiness 0.11. 数据管理 0.12. emptyDir 0.13. hostPath 0.14. PV & PVC 0.15. 回收PV和PVC 0.16. PV动态供给 0.17. Secret & Configmap 0.17.1. 创建Secret 0.18. 使用Secret 0.19. 创建ConfigMap 0.20. 使用ConfigMap 0.21. 网络 0.22. kubernetes网络模型 0.22.1. pod内容器之间的通信 0.22.2. pod之间通信 0.22.3. Pod与Service通信 0.22.4. 外部访问 0.23. CNI 0.24. Network Policy 0.25. 其他概念 0.26. Service Account & User Account 0.27. Security Context & PSP 0.27.1. Container-level Security Context 0.27.2. Pod-level Security Context 0.27.3. Pod Security Policies(PSP) 0.28. Resource Quotas 0.28.1. 资源配额的启用 0.28.2. 资源配额的类型 0.28.3. LimitRange 0.28.3.1. 配额范围 0.29. 什么是Ingress 0.30. ConfigMap 0.30.1. ConfigMap创建 0.30.1.1. 从key-value字符串创建ConfigMap 0.30.1.2. 从env文件创建 0.30.1.3. 从目录创建 0.30.2. ConfigMap使用 0.1. 概念 Cluster:计算、存储、网络资源的合集,利用这些资源运行各种基于容器的应用 Master:Cluster的大脑,主要职责是调度,(为了实现高可用,可以运行多个Master) Node:主要职责运行容器应用,由Master管理,负责监控并汇报容器状态,同时根据Master的要求管理容器的生命周期 Pod:K8S的最小工作单位,每个Pod中包含一个或多个容器,Pod中的容器作为一个整体被调度,扩展,共享资源,管理生命周期 Kubernetes 引入Pod的目的: 1、可管理性(提供了比容器更高层次的抽象,将他们封装在一个部署单元) 2、通信和资源共享(Pod中的容器在一个网络命名空间下,相同的IP地址和Port空间,可以通过localhost通信,可以共享存储) Pod使用方式: (1)运行单一容器 (2)运行多个容器(这些容器联系非常紧密,且需要直接共享资源) Controller:管理Pod,定义了Pod的部署特性(副本数,运行Node位置等) 满足不同的应用场景,Kubernetes提供了多种Controller(Deployment、ReplicaSet、DaemonSet、StatefulSet、Job等) (1)Deployment:管理Pod多副本,并确保Pod按照期望的状态运行 (2)ReplicaSet:Deployment通过ReplicaSet来实现Pod多副本管理(通常不直接使用ReplicaSet) (3)DaemonSet:用于每个Node只能运行一个Node副本的场景 (4)StatefulSet:保证Pod的每个副本在整个生命周期中名称不变(保证副本按照固定的顺序启动、更新、删除) (5)Job:用于运行结束就删除的应用 Service:定义外界访问一组特定Pod的方式,Service有自己的IP和端口,并且为Pod提供了负载均衡 Kubernetes 运行容器和访问容器的任务分布由Controller和Service执行。 Namespace:将一个物理的Cluster逻辑上会分为多个Cluster kubernetes 默认有两个Namespace: (1)default:创建资源时如果不指定,将会放在这个地方 (2)kube-system:kubernetes自己创建的系统资源会放在这个地方 0.2. Kubernetes架构 Kubernetes Cluster由Master和Node组成,节点上运行着若干Kubernetes服务 0.2.1. Master 运行的Daemon服务包括: kube-apiserver kube-scheduler kube-controller-manager etcd Pod网络(如flannel) 0.2.1.1. API Server(kube-apiserver) 提供HTTP/HTTPS RESTful API,是集群的前端接口,各种客户端工具(CLI或UI)以及Kubernetes其他组件可以通过它管理集群的各种资源 0.2.1.2. Scheduler(kube-scheduler) 负责决定将Pod放在哪个Node上运行,Scheduler在调度时会充分考虑网络拓扑结构,当前各节点负载,以及应用对高可用,性能和数据亲和性的需求。 0.2.1.3. Controller Manager(kube-controller-manager) 管理集群各种资源,保证资源处于预期状态 Controller Manager由多种Controller组成: controller 作用 replication Controller 管理Deployment、StatefulSet、DaemonSet的生命周期 namespace Controller 管理Namespace资源 0.2.2. etcd 保存集群的配置信息和各种资源的状态信息,当数据发生变化时,etcd会快速通知Kubernetes的相关组件 0.2.3. Pod网络 Pod要能够正常通信,Kubernetes 集群必须部署Pod网络,flannel为其中一个可选方案 0.2.4. Node kubernetes支持Docker 、 rkt等容器Runtime,Node上运行的Daemon服务包括: kubelet kube-proxy Pod网络(如flannel) 0.2.4.1. Kubelet 是Node的agent,当scheduler确定在某个Node上运行Pod后,将Pod具体的配置信息(image、volume等)发送给该节点的kubelet,kubelet根据这些信息创建并运行容器,并向Master报告运行状态 0.2.4.2. kube-proxy Service在逻辑上代表了后端的多个pod,外界通过service访问pod。kube-proxy负责将访问Service的TCP/UDP数据流转发到后端的容器,如果有多个副本,kube-proxy会实现负载均衡 0.2.5. Pod网络 同上 部署完成的Kubernetes集群中运行如下pod: 这些pod都是运行在kube-system命名空间中,kube-dns组件是为集群提供DNS服务,只有kubelet没有容器化,通过systemd服务进行管理,如下图所示: [root@tdc-01 ~]# systemctl status kubelet.service ● kubelet.service - Kubernetes Kubelet Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled) Active: active (running) since 五 2018-10-12 02:07:46 CST; 13h ago Main PID: 2586 (kubelet) CGroup: /system.slice/kubelet.service ├─2586 /opt/kubernetes/bin/kubelet --logtostderr=true --v=4 --address=0.0.0.0 --cluster_dns=10.10.0.10 --cluster_domain=transwarp.local --port=10250 --hostname_override=172.16... └─2671 journalctl -k -f 0.3. 运行应用 0.3.1. Deployment kubernetes 支持两种创建资源的方式: 用kubectl命令直接创建,比如: kubectl run Nginx-deployment --image=nginx:1.7.9 --replicas=2 通过配置文件和kubectl apply创建资源 基于命令的方式简单、直观、快捷、适合临时测试或实验 基于配置文件的方式描述详细,模板可重复部署,适合正式的跨环境的规模化部署 0.3.2. YAML配置文件格式 apiVersion: extensions/v1beta1 //当前配置格式的版本 kind: Deployment //资源的类型 metadata: //资源的元数据 name: wdg4f namespace: kube-system spec: //该Deployment的规格 replicas: 3 //副本数 template: //pod的模板 metadata: //pod的元数据 labels: //至少定义一个labels app:server spec: //该pod的规格,pod的名字和镜像地址等 containers: - name: images: nodeSelector: ① type:gpu 0.3.3. 用label控制Pod的位置 比如,不同的节点上配置不同的资源,不同的pod调度到对应的node上,使用对应的资源 kubectl lable node node-1 type=gpu //给node贴label kubectl get node --show-labels //查看node的label kubectl label node node-1 type- //删除对应的label node已经贴好对应的标签,在deployment中定义的pod中配置好对应的nodeSelector即可,如上配置文件中的①位置。 0.4. DaemonSet 每个Node上最多只能运行一个副本,典型的应用场景如下: 在集群的各个节点上运行存储Daemon:比如glusterd或ceph 在每个节点上运行日志收集Daemon:比如flunentd或logstash 在每个节点上运行监控Daemon:比如prometheus Node Exporter 或 collectd kubernetes 自己就在用DaemonSet运行系统组件,如kube-proxy和kube-fannel-ds 0.4.1. Job 容器按照持续运行的时间可以分为:服务类容器和工作类容器 服务类容器(Deployment、ReplicaSet、Daemon):持续提供服务,需要一直运行,如HTTP Server 工作类容器(Job):一次性任务,如批处理程序 0.4.1.1. Job并行 如果希望同时执行多个pod来提高执行效率,在Job的配置文件中添加parallelism参数,类设置并发数,该参数添加在Job配置文件的spec中。 apiVersion: extensions/v1beta1 //当前配置格式的版本 kind: Job //资源的类型 metadata: //资源的元数据 name: jobs spec: //该Job的规格 parallelism:3 //并行数,默认为1 completions:9 //设置Job成功完成pod的总数,默认为1 template: //pod的模板 以上配置文件的意思,每次并发运行3个pod,直到总共有9个pod成功完成。 0.4.2. 定时Job 在Linux中有一个cron程序定时执行任务,kubernetes中的cronJob提供类似功能 apiVersion: extensions/v1beta1 //当前配置格式的版本 kind: CronJob //资源的类型 metadata: //资源的元数据 name: jobs spec: //该Job的规格 scheduler: "*/****" //与Linux中cron的语法一致 jobTemplate: //pod的模板 spec: template: kubernetes默认没有开启定时任务功能,需要在kube-apiserver中添加配置,修改配置文件/etc/kubernetes/manifests/kube-apiserver.yaml,在command中添加 --runtime-config=batch/v2alpha1=true 或者在kube-apiserver这个pod启动的时候以启动参数的形式添加,并重启kubelet服务 0.5. Service访问pod Service 从逻辑上代表了一组pod,通过label来选择对应pod。 Pod有自己的IP,这个pod-IP会随着Pod的销毁而改变,被kubernetes集群中的容器和节点访问 Service也有自己的IP,这个cluster-IP不会变 客户端只需要访问Service的IP,kubernetes负责建立和维护Service和Pod的映射关系。 apiVersion: v1 //当前配置格式的版本 kind: Service //资源的类型 metadata: //资源的元数据 name: svc spec: //该Service的规格 selector: run:httpd ports: - protocol:TCP port:8080 //service暴露端口 targetPort:80 //映射的pod的端口 cluster-IP和pod-IP通过iptables实现映射 0.6. Cluster-IP 底层实现 cluster-ip是一个虚拟的IP,有kubernetes节点上的iptabels规则管理。 iptables将访问Service的流量转发到后端Pod,使用类似轮询的负载均衡策略。 kubernetes集群中的每一个节点都配置了相同的iptables规则,这样确保整个集群都能够通过Service的Cluster IP访问到Service 0.7. DNS访问Service 在集群中除了通过Cluster IP访问Service还可以通过DNS访问,在kube-system命名空间中运行着kube-dns组件,这是一个DNS服务器,当有新的Service创建时,kube-dns会添加该Service的DNS记录。 集群中的pod可以通过.访问到Service,例如: wget SVC.default:8080 nslookup svc.default //查看service的DNS Server: 10.96.0.10 Address: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: SVC Address: 10.99.229.179 svc.default.svc.cluster.local DNS服务器是kube-dns.kube-system.svc.cluster.local,这个就是kube-dns组件,它是kube-system命名空间中的一个service svc.default.svc.cluster.local是svc的完整域名 0.8. 外网访问Service 除了集群内部可以访问Service,很多时候希望应用的Service能够暴露给集群外部,kubernetes提供了多种类型的Service,默认的是ClusterIP。 Cluster IP:Service通过集群内部的IP地址对外提供服务,只有集群内部的节点或Pod能访问 NodePort:Service通过集群中节点的静态端口对外提供服务,集群外部可以通过.访问Service LoadBalancer: 云提供商负责将load balancer的流量导向Servcice NodePort apiVersion: v1 kind: Service metadata name: svc spec type:NodePort selector: run:httpd ports: - protocol:TCP nodeport:3000 //配置这个参数,则指定了service的nodeport 【节点监听的端口】 port:8080 //service暴露端口 【cluster IP 监听的端口】 targetPort:80 //映射的pod的端口 【pod监听的端口】 以上配置文件创建的Service中不但有cluster IP,同时还有External IP 为Nodes 并且将service的8080端口和节点的静态端口进行绑定,kubernetes会从30000~32767中随机分配一个,每个节点都会监听这个端口,并将请求转发给Service。 同样,通过iptables将访问节点端口的流量转发给service的后台pod。 0.9. 健康检查 强大的自愈能力是Kubernetes这类容器编排引擎的重要特性,默认的实现是自动重启发生故障的容器。也可以使用Liveness和Readiness 探测机制设置更精细的健康检查,从而实现如下需求: 零停机部署 避免部署无效镜像 更加安全的滚动升级 0.9.1. 默认的健康检查 每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。如果该进程退出时的返回码非零,则认为容器发生故障,Kubernetes就根据restartPolicy重启容器。 restartPolicy: Always(默认) OnFailure Never 以上情况适用于,pod发生故障后进程退出,且退出码不为0。但是存在一些情况,pod发生故障,但是进程并没有退出,比如系统超负载或者资源死锁等,此时进程并没有异常退出的,在这种情况下重启容器是最直接有效的解决方案。 处理以上情况使用Liveness探测。 0.9.2. Liveness探测 Liveness探测让用户可以自定义判断容器是否健康的条件。 apiVersion: v1 kind: Pod metadata labels: test:liveness name: liveness spec restartPolicy:OnFailure containers: - name: liveness image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 10 periodSeconds: 5 以上pod的配置文件中,启动进程首先创建文件/tmp/healthy 30秒后删除; 判断条件为如果文件存在,则任务容器正常。 livenessProbe部分定义了如何执行Liveness操作: 探测方法:通过cat命令检查文件是否存在,如果存在返回0,否则返回非0 initialDelaySeconds:指定容器启动后10秒开始执行Liveness探测(一般根据应用启动的准备时间来设置该参数) periodSeconds:指定每隔多少时间执行一次Liveness探测,如果连续执行三次都是失败则会杀掉并重启容器。 kubectl describe pod liveness //查看liveness日志 Liveness探测 告诉kubernetes什么时候通过重启容器实现自愈 0.10. Readiness探测 Readiness探测 告诉kubernetes什么时候可以将容器加入到Service负载均衡池中,对外提供服务。 Readiness语法和Liveness完全一致: apiVersion: v1 kind: Pod metadata labels: test:readiness name: readiness spec restartPolicy:OnFailure containers: - name: readiness image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 readinessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 10 periodSeconds: 5 以上配置文件创建的pod 在经过initialDelaySeconds+periodSeconds之后开始进行Readiness探测,探测成功后pod状态被设置为Ready kubectl describe pod readiness //查看readiness日志 0.10.1. liveness与readiness 如果不进行特殊的配置,kubernetes将对这两个探测采取相同的默认行为,通过判断容器启动进程的返回值是否为零来判断探测是否成功。 两者的配置方法一样,支持的配置参数一样,不同之处在于: liveness探测后重启容器 readiness探测后将容器设置为不可用 liveness探测和readiness探测是独立运行的,两者之间没有依赖: liveness探测判断容器是否需要重启以实现自愈 readiness探测判断容器是否已经准备好对外提供服务 0.11. 数据管理 容器销毁时,保存在容器内部文件系统中的数据会被清除,为了持久化保存容器中的数据,使用kubernetes Volume。Volume的生命周期独立于容器,Pod中的容器可能被销毁和重建,但是volume会被保留。 本质上Volume是一个目录,当volume被mount到pod中时,pod中的所有容器都可以访问这个volume。 volume支持多种backend: emptyDir hostPath GCE Persisten Disk AWS Elastic Block Store NFS Ceph 等 volume提供了对各种backend的抽象,容器在使用volume读写数据时不需要关心数据到底是存放在本地节点的文件系统,还是云硬盘上。所有类型的volume都只是一个目录。 0.12. emptyDir 最基础的volume类型,是host上的一个空目录,对于容器是持久化的,对于pod不是持久化的。当pod从节点删除时volume 的内容也会被删除。 emptyDir Volume的生命周期与Pod一致。pod中所有容器可以共享volume,它们可以指定各自的mount路径 emptyDir是Docker Host文件系统里的目录,其效果相当于执行了docker -v /dir ,通过docker inscept 可以查看到容器的详细配置 /var/lib/kubelet/pods/062b215e-c0b4-11e8-badb-0cc47aa57eee/volumes/kubernetes.io~empty-dir/dir 这个是emptyDir在host上的真正路径。 0.13. hostPath hostPath的作用是将Docker Host文件系统中已经存在的目录mount给pod的容器。 大部分应用都不会使用hostPath(这实际上增加了pod与节点的耦合程度),限制了Pod的使用。那些需要访问kubernetes或docker内部数据(配置文件或二进制库)的应用需要使用hostPath。 如kube-apiserver和kube-controller-manager就是这样的应用。 当pod销毁,hostPath对应的目录还是会被保留的,只有当节点奔溃才会导致hostPath不可用。 0.14. PV & PVC PV:外部存储系统中的一块存储空间,具有持久性,生命周期独立于Pod PVC: 对PV的申请(Claim),需要为Pod分配存储资源时,用户可以创建一个PVC,指明存储资源的容量大小和访问模式等,kubernetes会查找并提供满足条件的PV 有了PVC用户只需要告诉kubernetes需要什么样的存储资源,而不关心真正的空间从哪里分配,如何访问等底层细节。 apiVersion: v1 kind: PersistentVolume metadata name:mypv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle StorageClassName: nfs //指定PV的class的分类,pvc可以指定class申请相应class的PV nfs: path: /nfs/data server: 192.168.1.1 PV 支持的类型: AWS EBS Ceph NFS 等 PV有三种访问模式(accessModes): ReadWriteOnce:以read-write模式mount到单节点 ReadOnlyMany:以read-only模式mount到多个节点 ReadWriteMany:以read-write模式mount到多个节点 PV有三种回收策略(persistentVolumeReclaimPolicy): Retain:手工回收 Recycle:清除PV中的数据,效果类似于rm -rf /the volume / * Delete:删除Store Provider 上的对应存储资源,例如AWS EBS、GCE PD、Azure Disk、Openstack Cinder Volume等 apiVersion: v1 kind: PersistentVolumeClaim metadata name:mypvc1 spec: accessModes: - ReadWriteOnce resources: request: storage:1Gi StorageClassName:nfs //pvc指定class申请相应class的PV 在pod的配置文件中使用对应的pvc即可,就像使用普通Volume的格式那样使用。 0.15. 回收PV和PVC 当不需要使用PV时,删除PVC然后回收PV PV的状态: Available:可以被PVC申请,可能是新创建的或者是被释放并且数据清除完成的 Bound:已经被PVC使用 Released:解除了pvc对该PV的bound,并根据PV的回收策略,对其中的数据进行操作: 策略为Recycle,此时正在清除其中的数据(kubernetes启动一个新的pod去删除PV中的数据) 策略为Retain,数据需要手动回收,不会被自动删除,PV一直处于released状态,不能被再次申请(删除该PV并重新创建,可以使得它能够再次本申请,此时原来的PV中的数据不会被删除,相当于重新创建了一个新的同名的PV,在物理存储介质中占用了一块新存储空间) 0.16. PV动态供给 上面的例子都是手动创建PV,然后手动创建PVC去bound对应的PV,然后pod手动mount对应的pvc,以上过程,成为静态供给 动态供给的意思是,如果没有满足PVC条件的PV,会动态创建PV,相比于静态供给的优势在于++不需要提前创建好PV++,减少工作量,提高效率。 动态供给是通过StorageClass实现的,在StorageClass中定义好如何创建PV,如下: StrorageClass standard: apiVersion:storage.k8s.io/v1 kind:StorageClass metadata: name:standard provisioner:kubernetes.io/aws-ebs parameters: type:gp2 reclaimPolicy:Retain StorageClass slow: apiVersion:storage.k8s.io/v1 kind:StorageClass metadata: name:slow provisioner:kubernetes.io/aws-ebs parameters: type:io1 zones: us-east-1d,us-east-1c iopsPerGB: "10" StorageClass 支持delete和retain两种reclaimPolicy,默认为Delete,PVC配置文件中只需要指定storageClassName:standard/slow,其他都一样 kubernetes支持动态供给PV的provisioner如下: https://kubernetes.io/docs/concepts/storage/storage-classes/ 官网中有表格 0.17. Secret & Configmap 应用启动过程中可能需要一些敏感数信息,如访问数据库的用户名,密码等,将这些信息直接保存在容器镜像中显然不妥。Secret会以密文的方式存储数据,避免了直接在配置文件中保存敏感信息。 Secret会以Volume的形式mount到pod,容器可以通过文件的形式使用Secret中的敏感数据,容器㛑能够以环境变量的形式使用这些数据。 0.17.1. 创建Secret 命令行创建 每个--from-literal对应一个信息条目 kubectl ceate secret generic mysecret --from-literal=username=admin --from-literal=password=123456 每个文件内容对应一个条目 kubectl ceate secret generic mysecret --from-file=./username --from-file=./password 文件env.txt中每一行key=value对应一个信息条目 kubectl ceate secret generic mysecret --from-env-file=env.txt YAML文件创建 apiVersion: v1 kind: secret metadata: name: mysecret data: username: YWRtaW4= password: MTIzNDU2 文件中的敏感数据必须通过base64编码,然后执行kubectl apply 创建secret 0.18. 使用Secret volume方式使用 apiVersion:v1 kind:Pod metadata: name:mypod spec: containers: - name:mypod image:busybox volumeMounts: - name:foo mountPath: "/etc/foo" readOnly: true volumes: - name:foo secret: secretName:mysecret items: //自定义敏感数据的存储路径 - key:username path: my-group/my-username - key: password path: my-group/my-password kubernetes会在指定的路径下为每一条敏感数据创建一个文件,文件名就是敏感数据条目的key值,value则以明文存放在对应的文件中,Secret动态更新,容器中的数据也会动态更新。 环境变量方式使用 通过volume使用secret,容器必须从文件读取数据,还可以通过环境变量使用Secret。 piVersion:v1 kind:Pod metadata: name:mypod spec: containers: - name:mypod image:busybox env: - name: SECRET_USERNAME valueFrom: secretkeyRef: name: mysecret key: username - name: SECRET_PASSWORD valueFrom: secretkeyRef: name: mysecret key: password pod 通过环境变量SECRET_USERNAME和SECRET_PASSWORD成功读取到Secret中的数据。以环境变量的方式虽然读取方便,但是无法动态更新。 0.19. 创建ConfigMap Secret可以为Pod提供敏感数据,对于非敏感数据,如配置信息,可以使用ConfigMap。 命令行创建 每个--from-literal对应一个信息条目 kubectl ceate configmap generic myconfigmap --from-literal=config1=xxx --from-literal=config2=yyy 每个文件内容对应一个条目 kubectl ceate configmap generic myconfigmap --from-file=./config1 --from-file=./config2 文件env.txt中每一行key=value对应一个信息条目 kubectl ceate configmap generic myconfigmap --from-env-file=env.txt YAML文件创建 apiVersion: v1 kind: configmap metadata: name: myconfigmap data: config1: xxx config2: yyy 文件数据直接以明文输入,然后执行kubectl apply 创建configmap 0.20. 使用ConfigMap volume方式使用 apiVersion:v1 kind:Pod metadata: name:mypod spec: containers: - name:mypod image:busybox volumeMounts: - name:foo mountPath: "/etc" //将volume mount到/etc路径下 readOnly: true volumes: - name: foo configmap: name:myconfigmap items: - key: logging.conf path: myapp/logging.conf //在volume中指定存放配置文件的相对路径 这种方式配置文件支持动态更新。 环境变量方式使用 piVersion:v1 kind:Pod metadata: name:mypod spec: containers: - name:mypod image:busybox env: - name: CONFIG_1 valueFrom: configMapkeyRef: name: myconfigmap key: config1 - name: CONFIG_2 valueFrom: configMapkeyRef: name: myconfigmap key: config2 pod 通过环境变量CONFIG_1和CONFIG_2成功读取到ConfigMap中的数据。 大多数情况下,配置信息以文件的形式提供,使用--from-file或YAML方式创建ConfigMap,读取ConfigMap通常采用Volume的方式 0.21. 网络 Pod、 Service、 外部组件之间需要一种可靠的方式找到彼此并进行通信,kubernetes网络负责提供这个保障。 0.22. kubernetes网络模型 采用基于扁平地址的网络模型,每个pod都要自己的IP,相互之间不需要配置NAT(网络地址转换)就能够直接通信,同一个Pod中的容器共享pod的IP,能够通过Localhost通信。 这种网络模型可以很方便的迁移传统应用到Kubernetes,每个pod可以被看做一个独立的系统,而Pod中的容器可以被看作同一系统中的不同进程。 0.22.1. pod内容器之间的通信 当pod被调度到节点后,pod中的所有容器都运行在这个节点上,这些容器共享本地文件系统,IPC和网络命名空间等。 不同pod之间不存在端口冲突问题,每个pod有自己独立的IP地址。当某个容器使用localhost时,意味着使用的是容器所属pod的地址空间。 0.22.2. pod之间通信 Pod的IP地址是集群可见的,任何其他Pod和节点都可以通过IP直接与Pod通信,这种通信不需要借助任何网络地址转换、隧道或代理技术。Pod内部和外部使用的是同一个IP,这也意味着标准的命名服务和法发现机制,比如DNS都可以直接使用。 0.22.3. Pod与Service通信 Pod会被频繁的销毁和创建,因此pod的IP是不固定的,Service提供了访问pod的抽象层,无论后端Pod如何变化,Service都作为稳定的前端对外提供服务,同时,Service还提供了高可用和负载均衡功能,Service负责将请求转发给正确的Pod。 0.22.4. 外部访问 无论Pod的IP还是Service的Cluster IP 都是集群内部的私有IP,kubernetes提供以下两种方式让外界能够与Pod通信: NodePort:Service通过集群节点的静态端口对外提供服务,外部可以通过.访问Service。 LoadBalancer:Service利用Cloud provider 提供的load balancer 对外提供服务,cloud provider 负责将 load balancer的流量导向service。 0.23. CNI 为了保证网络方案的标准化、扩展性、灵活性,采用CNI规范,使用插件模型创建容器的网络栈。 CNI的优点是支持多种容器runtime,CNI的插件模型支持不同组织和公司开发的第三方插件,可以灵活选择合适的网络方案。 目前已经有多种支持Kubernetes的网络方案,比如Flannel、Calico、Canal、Weave Net等,这些不同的方案底层实现不同,有的基于VxLAN的Overlay,有的采用Underlay,性能上有区别。 0.24. Network Policy Network Policy是kubernetes的一种资源,通过Label来选择Pod,并制定其他Pod或外界如何与这些Pod通信。 默认情况下,所以Pod是非隔离的,即任何来源的网络流量都能够访问Pod,没有任何限制。当为Pod定义了Network Policy后,只有Policy允许的流量才能够访问Pod。 不是所有的网络方案都支持Network Policy,(Fannel不支持,calico支持)。 apiVersion: networing.k8s.io/v1 kind:NetworkPolicy metadata: name: access-httpd spec: podSelector: matchLabels: run: httpd //规则应用到有这个label的pod上 ingress: - from: - podSelector matchLabels: access: "true" //只有label为access:"true"的pod能访问 ports: - protocol: TCP port: 80 //只能访问端口80 0.25. 其他概念 0.26. Service Account & User Account User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API而设计; User account是跨namespace的,而service account则是仅局限它所在的namespace; 每个namespace都会自动创建一个default service account Token controller检测service account的创建,并为它们创建secret 开启ServiceAccount Admission Controller后: 每个Pod在创建后都会自动设置spec.serviceAccount为default(除非指定了其他ServiceAccout) 验证Pod引用的service account已经存在,否则拒绝创建 如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中 每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/ Service Account为服务提供了一种方便的认证机制,但它不关心授权的问题。可以配合RBAC来为Service Account鉴权: 配置–authorization-mode=RBAC和–runtime-config=rbac.authorization.k8s.io/v1alpha1 配置–authorization-rbac-super-user=admin 定义Role、ClusterRole、RoleBinding或ClusterRoleBinding 0.27. Security Context & PSP Security Context的目的是限制不可信容器的行为,保护系统和其他容器不受其影响。 Kubernetes提供了三种配置Security Context的方法: Container-level Security Context:仅应用到指定的容器 Pod-level Security Context:应用到Pod内所有容器以及Volume Pod Security Policies(PSP):应用到集群内部所有Pod以及Volume 0.27.1. Container-level Security Context Container-level Security Context仅应用到指定的容器上,并且不会影响Volume。比如设置容器运行在特权模式。 0.27.2. Pod-level Security Context Pod-level Security Context应用到Pod内所有容器,并且还会影响Volume(包括fsGroup和selinuxOptions)。 0.27.3. Pod Security Policies(PSP) Pod Security Policies(PSP)是集群级的Pod安全策略,自动为集群内的Pod和Volume设置Security Context。 使用PSP需要API Server开启extensions/v1beta1/podsecuritypolicy,并且配置PodSecurityPolicyadmission控制器。 支持的控制项 控制项| 说明 ---|--- privileged |运行特权容器 defaultAddCapabilities| 可添加到容器的Capabilities requiredDropCapabilities| 会从容器中删除的Capabilities volumes |控制容器可以使用哪些volume hostNetwork| host网络 hostPorts |允许的host端口列表 hostPID |使用host PID namespace hostIPC |使用host IPC namespace seLinux |SELinux Context runAsUser| user ID supplementalGroups| 允许的补充用户组 fsGroup| volume FSGroup readOnlyRootFilesystem |只读根文件系统 0.28. Resource Quotas 资源配额(Resource Quotas)是用来限制用户资源用量的一种机制。 它的工作原理为: 资源配额应用在Namespace上,并且每个Namespace最多只能有一个ResourceQuota对象 开启计算资源配额后,创建容器时必须配置计算资源请求或限制(也可以用LimitRange设置默认值) 用户超额后禁止创建新的资源 0.28.1. 资源配额的启用 首先,在API Server启动时配置ResourceQuota adminssion control;然后在namespace中创建ResourceQuota对象即可。 0.28.2. 资源配额的类型 计算资源,包括cpu和memory cpu, limits.cpu, requests.cpu memory, limits.memory, requests.memory 存储资源,包括存储资源的总量以及指定storage class的总量 requests.storage:存储资源总量,如500Gi persistentvolumeclaims:pvc的个数 .storageclass.storage.k8s.io/requests.storage .storageclass.storage.k8s.io/persistentvolumeclaims 对象数,即可创建的对象的个数 pods, replicationcontrollers, configmaps, secrets resourcequotas, persistentvolumeclaims services, services.loadbalancers, services.nodeports 0.28.3. LimitRange 默认情况下,Kubernetes中所有容器都没有任何CPU和内存限制。LimitRange用来给Namespace增加一个资源限制,包括最小、最大和默认资源。 0.28.3.1. 配额范围 每个配额在创建时可以指定一系列的范围 范围 说明 Terminating podSpec.ActiveDeadlineSeconds>=0的Pod NotTerminating podSpec.activeDeadlineSeconds=nil的Pod BestEffort 所有容器的requests和limits都没有设置的Pod(Best-Effort) NotBestEffort 与BestEffort相反 0.29. 什么是Ingress 通常情况下,service和pod的IP仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到service在Node上暴露的NodePort上,然后再由kube-proxy将其转发给相关的Pod。 而Ingress就是为进入集群的请求提供路由规则的集合,如下图所示: graph LR Internet--> Ingress Ingress--> Service Ingress可以给service提供集群外部访问的URL、负载均衡、SSL终止、HTTP路由等。为了配置这些Ingress规则,集群管理员需要部署一个Ingress controller,它监听Ingress和service的变化,并根据规则配置负载均衡并提供访问入口。 Ingress格式: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress spec: rules: - http: paths: - path: /testpath backend: serviceName: test servicePort: 80 每个Ingress都需要配置rules,目前Kubernetes仅支持http规则。上面的示例表示请求/testpath时转发到服务test的80端口。 根据Ingress Spec配置的不同,Ingress可以分为以下几种类型: 单服务Ingress 单服务Ingress即该Ingress仅指定一个没有任何规则的后端服务。 注:单个服务还可以通过设置Service.Type=NodePort或者Service.Type=LoadBalancer来对外暴露。 路由到多服务的Ingress 路由到多服务的Ingress即根据请求路径的不同转发到不同的后端服务上,比如 graph LR foo.bar.com --> 178.91.123.132 178.91.123.132 --> /foo.s1:80 178.91.123.132 --> /bar.s2:80 可以通过下面的Ingress来定义: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: serviceName: s1 servicePort: 80 - path: /bar backend: serviceName: s2 servicePort: 80 TLS Ingress TLS Ingress通过Secret获取TLS私钥和证书(名为tls.crt和tls.key),来执行TLS终止。如果Ingress中的TLS配置部分指定了不同的主机,则它们将根据通过SNI TLS扩展指定的主机名(假如Ingress controller支持SNI)在多个相同端口上进行复用。 0.30. ConfigMap ConfigMap用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件。ConfigMap跟secret很类似,但它可以更方便地处理不包含敏感信息的字符串。 0.30.1. ConfigMap创建 可以使用kubectl create configmap从文件、目录或者key-value字符串创建等创建ConfigMap。 0.30.1.1. 从key-value字符串创建ConfigMap $ kubectl create configmap special-config --from-literal=special.how=very configmap "special-config" created $ kubectl get configmap special-config -o go-template='{{.data}}' map[special.how:very] 0.30.1.2. 从env文件创建 $ echo -e "a=b\nc=d" | tee config.env a=b c=d $ kubectl create configmap special-config --from-env-file=config.env configmap "special-config" created $ kubectl get configmap special-config -o go-template='{{.data}}' map[a:b c:d] 0.30.1.3. 从目录创建 $ mkdir config $ echo a>config/a $ echo b>config/b $ kubectl create configmap special-config --from-file=config/ configmap "special-config" created $ kubectl get configmap special-config -o go-template='{{.data}}' map[a:a b:b ] 0.30.2. ConfigMap使用 ConfigMap可以通过多种方式在Pod中使用,比如设置环境变量、设置容器命令行参数、在Volume中创建配置文件等。 注意 ConfigMap必须在Pod引用它之前创建 使用envFrom时,将会自动忽略无效的键 Pod只能使用同一个命名空间内的ConfigMap 用作环境变量 用作命令行参数 使用volume将ConfigMap作为文件或目录直接挂载