K8S 是负责自动化运维管理多个 Docker 程序的集群
K8S 是属于主从设备模型(Master-Slave 架构),即有 Master 节点负责核心的调度、管理和运维,Slave 节点则在执行用户的程序。但是在 K8S 中,主节点一般被称为Master Node 或者 Head Node(本文采用 Master Node 称呼方式),而从节点则被称为Worker Node 或者 Node(本文采用 Worker Node 称呼方式)。
要注意一点:Master Node 和 Worker Node 是分别安装了 K8S 的 Master 和 Woker 组件的实体服务器,每个 Node 都对应了一台实体服务器(虽然 Master Node 可以和其中一个 Worker Node 安装在同一台服务器,但是建议 Master Node 单独部署),所有 Master Node 和 Worker Node 组成了 K8S 集群,同一个集群可能存在多个 Master Node 和 Worker Node。
首先来看Master Node都有哪些组件:
接着来看Worker Node的组件
总结来看,
K8S 的 Master Node 具备:
而 K8S 的 Worker Node 具备:
Pod、Service、Volume 和 Namespace 是 Kubernetes 集群中四大基本对象,它们能够表示系统中部署的应用、工作负载、网络和磁盘资源,共同定义了集群的状态。Kubernetes 中很多其他的资源其实只对这些基本的对象进行了组合。
Pod是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod 的设计理念是支持多个容器在一个 Pod 中共享网络地址和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。
Pod 代表着集群中运行的进程:共享网络、共享存储
Pod 可以被理解成一群可以共享网络、存储和计算资源的容器化服务的集合。再打个形象的比喻,在同一个 Pod 里的几个 Docker 服务/程序,好像被部署在同一台机器上,可以通过 localhost 互相访问,并且可以共用 Pod 里的存储资源(这里是指 Docker 可以挂载 Pod 内的数据卷,数据卷的概念)
同一个 Pod 之间的 Container 可以通过 localhost 互相访问,并且可以挂载 Pod 内所有的数据卷;但是不同的 Pod 之间的 Container 不能用 localhost 访问,也不能挂载其他 Pod 的数据卷。
K8S 中所有的对象都通过 yaml 来表示,笔者从官方网站摘录了一个最简单的 Pod 的 yaml:
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
首先,我们需要知道的是,每个 Pod 都有一个特殊的被称为 “根容器” 的 Pause 容器。Pause 容器对应的镜像属于 Kubernetes 平台的一部分,通过 Pause 容器使工作在对应 Pod 的容器之间可以共享网络、共享存储。Pod内部结构如下图所示:
[1] Pod 共享资源
为什么 Kubernetes 会设计出一个全新的 Pod 概念,并且有这样特殊的结构?主要是因为,使用 Pause 容器作为 Pod 根容器,以它的状态代表整个容器组的状态;其次,Pod 里的多个业务容器共享 Pause 容器的 IP 地址,共享 Pause 容器挂接的 Volume 资源。
集群网络解决方案: Kubernetes + Flannel
Kubernetes 的网络模型假定了所有 Pod 都在一个直接连通的扁平的网络空间中,这在 GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes 假定这个网络已经存在了。而在私有云搭建 Kubernetes 集群,就不能假定这个网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的 Docker 容器之间的互相访问先打通,然后才能正常运行 Kubernetes 集群。
Flannel 是 CoreOS 团队针对 Kubernetes 设计的一个网络规划服务,简单来说,它的功能就是让集群中的不同节点主机创建的 Docker 容器都具有全集群唯一的虚拟 IP 地址。而且它还能在这些 IP 地址之间建立一个覆盖的网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递给目标容器内。
[2] ETCD 之于 Flannel 提供说明:
Pod 存在多种不同的创建类型来满足不一样的用途
[1] ReplicationController
ReplicationController 用来确保容器应用的副本数量始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来代替,而如果异常多出现的容器会自动回收。
[2] ReplicaSet
在新版本(相对而言的较优方式)的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController 来管理 Pod。虽然 ReplicaSet 和 ReplicationController 并没有本质上的不同,只是名字不一样而已,唯一的区别就是 ReplicaSet 支持集合式的 selector,可供标签筛选。
虽然 ReplicaSet 可以独立使用,但一般还是建议使用 Deployment 来自动管理 ReplicaSet 创建的 Pod,这样就无需担心跟其他机制的不兼容问题。比如 ReplicaSet 自身并不支持滚动更新(rolling-update),但是使用 Deployment 来部署就原生支持。
[3] Deployment
Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义方法,用来替代以前使用 ReplicationController 来方便且便捷的管理应用。主要的应用场景,包括:滚动升级和回滚应用、扩容和缩容、暂停和继续。
[4] HPA
HPA 仅仅适用于 Deployment 和 ReplicaSet,在 V1 版本中仅支持根据 Pod 的 CPU 利用率扩缩容,在新版本中,支持根据内存和用户自定义的 metric 动态扩缩容。
[5] StatefulSet
StatefulSet 是为了解决有状态服务的问题,相对于 Deployment 和 ReplicaSet 而已。其主要的使用场景,包括:稳定的持久化存储、稳定的网络标识、有序部署、有序收缩。
[6] DaemonSet
DaemonSet 确保全部或者一些 Node 上面运行一个 Pod 副本。当有 Node 加入集群的时候,也会为它们新加一个 Pod。当有 Node 从集群中移除的时候,这些 Pod 也会被回收。删除 DaemonSet 将会删除它所创建的所有 Pod。
使用 DaemonSet 的典型场景就是,在每个节点运行日志收集、运行监控系统、运行集群存储等服务,只要新加进来的节点都需要运行该服务。
[7] Job
Job 负责批处理任务,仅执行一次的任务,它保证批处理任务的一个或者多个 Pod 成功结束,才会返回成功。
[8] Cront Job
Cront Job 管理是基于时间的 Job,即在给定时间点只运行一次,且周期行的在给定时间点运行特定任务
数据卷 volume 是 Pod 内部的磁盘资源。
volume 是 K8S 的对象,对应一个实体的数据卷;
volumeMounts 只是 container 的挂载点,对应 container 的其中一个参数。 但是,volumeMounts 依赖于 volume,只有当 Pod 内有 volume 资源的时候,该 Pod 内部的 container 才可能有 volumeMounts。
本文中提到的镜像 Image、容器 Container,都指代了 Pod 下的一个container。关于 K8S 中的容器,一个 Pod 内可以有多个容器 container。
除了 Pod 之外,K8S 中最常听到的另一个对象就是 Deployment 了。
Deployment 的作用是管理和控制 Pod 和 ReplicaSet,管控它们运行在用户期望的状态中。 Deployment 就是包工头,主要负责监督底下的工人 Pod 干活,确保每时每刻有用户要求数量的 Pod 在工作。如果一旦发现某个工人 Pod 不行了,就赶紧新拉一个 Pod 过来替换它。
那什么是 ReplicaSets 呢?
ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。
ReplicationController 是 ReplicaSet 的前身。
再来翻译下:ReplicaSet 的作用就是管理和控制 Pod,管控他们好好干活。但是,ReplicaSet 受控于 Deployment。形象来说,ReplicaSet 就是总包工头手下的小包工头。
前文介绍的 Deployment、ReplicationController 和 ReplicaSet 主要管控 Pod 程序服务;那么,Service 和 Ingress 则负责管控 Pod 网络服务。
K8S 中的服务(Service)并不是我们常说的“服务”的含义,而更像是网关层,是若干个 Pod 的流量入口、流量均衡器。
Service 是 K8S 服务的核心,屏蔽了服务细节,统一对外暴露服务接口,真正做到了“微服务”。 举个例子,我们的一个服务 A,部署了 3 个备份,也就是 3 个 Pod;对于用户来说,只需要关注一个 Service 的入口就可以,而不需要操心究竟应该请求哪一个 Pod。优势非常明显:一方面外部用户不需要感知因为 Pod 上服务的意外崩溃、K8S 重新拉起 Pod 而造成的 IP 变更,外部用户也不需要感知因升级、变更服务带来的 Pod 替换而造成的 IP 变化,另一方面,Service 还可以做流量负载均衡。
但是,Service 主要负责 K8S 集群内部的网络拓扑。那么集群外部怎么访问集群内部呢?这个时候就需要 Ingress 了,官方文档中的解释是:
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
Ingress 是整个 K8S 集群的接入层,复杂集群内外通讯。
Ingress 和 Service 的关系绘制网络拓扑关系图如下
namespace 则是为了服务整个 K8S 集群的。
官方文档定义
Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。这些虚拟集群被称为名字空间。
翻译一下:namespace 是为了把一个 K8S 集群划分为若干个资源不可共享的虚拟集群而诞生的。
可以通过在 K8S 集群内创建 namespace 来分隔资源和对象。
# 位于名字空间中的资源
kubectl api-resources --namespaced=true
# 不在名字空间中的资源
kubectl api-resources --namespaced=false
K8S 的对象实在太多了,其他的还有 Job、CronJob 等等。
官方文档中介绍 kubectl 是:
Kubectl 是一个命令行接口,用于对 Kubernetes 集群运行命令。Kubectl 的配置文件在$HOME/.kube 目录。我们可以通过设置 KUBECONFIG 环境变量或设置命令参数–kubeconfig 来指定其他位置的 kubeconfig 文件。
kubectl具体操作命令:
在 K8S 中,一个独立的服务即对应一个 Pod。即,当我们说要 xxx 一个服务的就是,也就是操作一个 Pod
查看:
kubectl 查看服务的基本命令是:
$ kubectl get|describe ${RESOURCE} [-o ${FORMAT}] -n=${NAMESPACE}
# ${RESOURCE}有: pod、deployment、replicaset(rs)
查看namespace:
$ kubectl get ns
-n=${NAMESPACE}就可以指定自己要操作的资源所在的 namespace。
查看 Pod:kubectl get pod -n=oona-test
查看 Deployment:kubectl get deployment -n=oona-test。
查看所有 namespace 下面部署的 Pod:
$ kubectl get pod --all-namespaces
查找所有的命名空间下的 Deployment 的命令是:
$ kubectl get deployment --all-namespaces
详细查看 -o wide
$ kubectl get pod [-o wide] -n=oona-test
————————————————————————————————————————————
更新/编辑
两种办法:1). 修改 yaml 文件后通过 kubectl 更新;2). 通过 kubectl 直接编辑 K8S 上的服务。
方法一:修改 yaml 文件后通过 kubectl 更新。我们看到,创建一个 Pod 或者 Deployment 的命令是kubectl create -f ${YAML}。
但是,如果 K8S 集群当前的 namespace 下已经有该服务的话,会提示资源已经存在:通过 kubectl 更新的命令是kubectl apply -f ${YAML},我们再来试一试:
(备注:命令kubectl apply -f ${YAML}也可以用于首次创建一个服务哦)
方法二:通过 kubectl 直接编辑 K8S 上的服务。命令为kubectl edit ${RESOURCE} ${NAME},
比如修改刚刚的 Pod 的命令为kubectl edit pod memory-demo,然后直接编辑自己要修改的内容即可。
————————————————————————————————————————————
删除
在 K8S 上删除服务的操作非常简单,命令为kubectl delete ${RESOURCE} ${NAME}。
比如删除一个 Pod 是:kubectl delete pod memory-demo,
比如删除一个 Deployment 的命令是:kubectl delete deployment ${DEPLOYMENT_NAME}。
如果是通过 Deployment 部署的服务,那么仅仅删除 Pod 是不行的,正确的删除方式应该是:先删除 Deployment,再删除 Pod。
有时候会发现一个 Pod 总也删除不了,这个时候很有可能要实施强制删除措施,
命令为kubectl delete pod --force --grace-period=0 ${POD_NAME}。
可以通过 kubectl 来操作 K8S 集群,基本语法:
kubectl [command] [TYPE] [NAME] [flags]
其中 command、TYPE、NAME 和 flags 分别是:
就如何使用 kubectl 而言,官方文档已经说得非常清楚。不过对于新手而言,还是需要解释几句:
安装并配置kubectl
第一步,必须准备好要连接/使用的 K8S 的配置文件,笔者给出一份杜撰的配置:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: thisisfakecertifcateauthoritydata00000000000
server: https://1.2.3.4:1234
name: cls-dev
contexts:
- context:
cluster: cls-dev
user: kubernetes-admin
name: kubernetes-admin@test
current-context: kubernetes-admin@test
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
token: thisisfaketoken00000
解读如下:
第二步,给 kubectl 配置上配置文件。.
--kubeconfig参数
。第一种办法是每次执行 kubectl 的时候,都带上--kubeconfig=${CONFIG_PATH}
。给一点温馨小提示:每次都带这么一长串的字符非常麻烦,可以用 alias 别名来简化码字量,比如alias k=kubectl --kubeconfig=${CONFIG_PATH}
。KUBECONFIG
环境变量。第二种做法是使用环境变量KUBECONFIG把所有配置文件都记录下来,即export KUBECONFIG=$KUBECONFIG:${CONFIG_PATH}
。接下来就可以放心执行 kubectl 命令了。$HOME/.kube/config
配置文件。第三种做法是把配置文件的内容放到$HOME/.kube/config
内。具体做法为:$HOME/.kube/config
不存在,那么cp ${CONFIG_PATH} $HOME/.kube/config
即可;$HOME/.kube/config
已经存在,那么需要把新的配置内容加到 $HOME/.kube/config
下。单单只是cat ${CONFIG_PATH} >> $HOME/.kube/config
是不行的,正确的做法是:KUBECONFIG=$HOME/.kube/config:${CONFIG_PATH} kubectl config view --flatten > $HOME/.kube/config
。解释下这个命令的意思:先把所有的配置文件添加到环境变量KUBECONFIG中,然后执行kubectl config view --flatten打印出有效的配置文件内容,最后覆盖$HOME/.kube/config
即可。请注意,上述操作的优先级分别是 1>2>3,也就是说,kubectl 会优先检查–kubeconfig,若无则检查KUBECONFIG,若无则最后检查$HOME/.kube/config,如果还是没有,报错。但凡某一步找到了有效的 cluster,就中断检查,去连接 K8S 集群了。
第三步:配置正确的上下文。
按照第二步的做法,如果配置文件只有一个 cluster 是没有任何问题的,但是对于有多个 cluster 怎么办呢?到这里,有几个关于配置的必须掌握的命令:
kubectl config get-contexts。列出所有上下文信息。
kubectl config current-context。
查看当前的上下文信息。其实,命令 1 线束出来的*所指示的就是当前的上下文信息。
kubectl config use-context ${CONTEXT_NAME}。更改上下文信息。
kubectl config set-context ${CONTEXT_NAME}|--current --${KEY}=${VALUE}。
修改上下文的元素。比如可以修改用户账号、集群信息、连接到 K8S 后所在的 namespace。
关于该命令,还有几点要啰嗦的:
config set-context可以修改任何在配置文件中的上下文信息,只需要在命令中指定上下文名称就可以。而–current 则指代当前上下文。
上下文信息所包括的内容有:cluster 集群(名称)、用户账号(名称)、连接到 K8S 后所在的 namespace,因此有config set-context严格意义上的用法:
kubectl config set-context [NAME|--current] [--cluster=cluster_nickname] [--user=user_nickname] [--namespace=namespace] [options]
(备注:[options]可以通过 kubectl options 查看)
K8S 核心功能就是部署运维容器化服务,因此最重要的就是如何又快又好地部署自己的服务了。本章会介绍如何部署 Pod 和 Deployment。
通过 kubectl 部署 Pod 的办法分为两步:1). 准备 Pod 的 yaml 文件;2). 执行 kubectl 命令部署
第一步:准备 Pod 的 yaml 文件。
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
继续解读:
name。Pod 下该容器名称,后面查找 Pod 下的容器的关键字段。
image。容器的镜像地址,K8S 会根据这个字段去拉取镜像。
resources。容器化服务涉及到的 CPU、内存、GPU 等资源要求。可以看到有limits和requests两个子项。
limits是 K8S 为该容器至多分配的资源配额;
而requests则是 K8S 为该容器至少分配的资源配额。 打个比方,配置中要求了 memory 的requests为 100M,而此时如果 K8S 集群中所有的 Node 的可用内存都不足 100M,那么部署服务会失败;又如果有一个 Node 的内存有 16G 充裕,可以部署该 Pod,而在运行中,该容器服务发生了内存泄露,那么一旦超过 200M 就会因为 OOM 被 kill,尽管此时该机器上还有 15G+的内存。
command。容器的入口命令。
args。容器的入口参数。
volumeMounts。容器要挂载的 Pod 数据卷等。请务必记住:Pod 的数据卷只有被容器挂载后才能使用!
第二步:执行 kubectl 命令部署。 有了 Pod 的 yaml 文件之后,就可以用 kubectl 部署了,命令非常简单:kubectl create -f ${POD_YAML}
。
第一步:准备 Deployment 的 yaml 文件。首先来看 Deployment 的 yaml 文件内容:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: rss-site
namespace: mem-example
spec:
replicas: 2
template:
metadata:
labels:
app: web
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
emory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
继续来看几个重要的字段:
第二步:执行 kubectl 命令部署。Deployment 的部署办法同 Pod:kubectl create -f ${DEPLOYMENT_YAML}
。由此可见,K8S 会根据配置文件中的kind
字段来判断具体要创建的是什么资源。
这里插一句题外话:部署完 deployment 之后,可以查看到自动创建了 ReplicaSet 和 Pod。
拉到最后看到Events部分:kubectl describe ${RESOURCE} ${NAME}
如果服务部署成功了,且状态为running,那么就需要进入 Pod 内部的容器去查看自己的服务日志了: