This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,339 @@
<audio id="audio" title="10 | Kubernetes一键部署利器kubeadm" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/53/99/53e5baf328edf104e60797b9f7d9bb99.mp3"></audio>
你好我是张磊。今天我和你分享的主题是Kubernetes一键部署利器之kubeadm。
通过前面几篇文章的内容,我其实阐述了这样一个思想:**要真正发挥容器技术的实力你就不能仅仅局限于对Linux容器本身的钻研和使用。**
这些知识更适合作为你的技术储备,以便在需要的时候可以帮你更快地定位问题,并解决问题。
而更深入地学习容器技术的关键在于,**如何使用这些技术来“容器化”你的应用。**
比如我们的应用既可能是Java Web和MySQL这样的组合也可能是Cassandra这样的分布式系统。而要使用容器把后者运行起来你单单通过Docker把一个Cassandra镜像跑起来是没用的。
要把Cassandra应用容器化的关键在于如何处理好这些Cassandra容器之间的编排关系。比如哪些Cassandra容器是主哪些是从主从容器如何区分它们之间又如何进行自动发现和通信Cassandra容器的持久化数据又如何保持等等。
这也是为什么我们要反复强调Kubernetes项目的主要原因这个项目体现出来的容器化“表达能力”具有独有的先进性和完备性。这就使得它不仅能运行Java Web与MySQL这样的常规组合还能够处理Cassandra容器集群等复杂编排问题。所以对这种编排能力的剖析、解读和最佳实践将是本专栏最重要的一部分内容。
不过,万事开头难。
作为一个典型的分布式项目Kubernetes的部署一直以来都是挡在初学者前面的一只“拦路虎”。尤其是在Kubernetes项目发布初期它的部署完全要依靠一堆由社区维护的脚本。
其实Kubernetes作为一个Golang项目已经免去了很多类似于Python项目要安装语言级别依赖的麻烦。但是除了将各个组件编译成二进制文件外用户还要负责为这些二进制文件编写对应的配置文件、配置自启动脚本以及为kube-apiserver配置授权文件等等诸多运维工作。
目前各大云厂商最常用的部署的方法是使用SaltStack、Ansible等运维工具自动化地执行这些步骤。
但即使这样这个部署过程依然非常繁琐。因为SaltStack这类专业运维工具本身的学习成本就可能比Kubernetes项目还要高。
**难道Kubernetes项目就没有简单的部署方法了吗**
这个问题在Kubernetes社区里一直没有得到足够重视。直到2017年在志愿者的推动下社区才终于发起了一个独立的部署工具名叫[kubeadm](https://github.com/kubernetes/kubeadm)。
这个项目的目的就是要让用户能够通过这样两条指令完成一个Kubernetes集群的部署
```
# 创建一个Master节点
$ kubeadm init
# 将一个Node节点加入到当前集群中
$ kubeadm join &lt;Master节点的IP和端口&gt;
```
是不是非常方便呢?
不过,你可能也会有所顾虑:**Kubernetes的功能那么多这样一键部署出来的集群能用于生产环境吗**
为了回答这个问题在今天这篇文章我就先和你介绍一下kubeadm的工作原理吧。
## kubeadm的工作原理
在上一篇文章《从容器到容器云谈谈Kubernetes的本质》中我已经详细介绍了Kubernetes的架构和它的组件。在部署时它的每一个组件都是一个需要被执行的、单独的二进制文件。所以不难想象SaltStack这样的运维工具或者由社区维护的脚本的功能就是要把这些二进制文件传输到指定的机器当中然后编写控制脚本来启停这些组件。
不过,在理解了容器技术之后,你可能已经萌生出了这样一个想法,**为什么不用容器部署Kubernetes呢**
这样我只要给每个Kubernetes组件做一个容器镜像然后在每台宿主机上用docker run指令启动这些组件容器部署不就完成了吗
事实上在Kubernetes早期的部署脚本里确实有一个脚本就是用Docker部署Kubernetes项目的这个脚本相比于SaltStack等的部署方式也的确简单了不少。
但是,**这样做会带来一个很麻烦的问题如何容器化kubelet。**
我在上一篇文章中已经提到kubelet是Kubernetes项目用来操作Docker等容器运行时的核心组件。可是除了跟容器运行时打交道外kubelet在配置容器网络、管理容器数据卷时都需要直接操作宿主机。
而如果现在kubelet本身就运行在一个容器里那么直接操作宿主机就会变得很麻烦。对于网络配置来说还好kubelet容器可以通过不开启Network Namespace即Docker的host network模式的方式直接共享宿主机的网络栈。可是要让kubelet隔着容器的Mount Namespace和文件系统操作宿主机的文件系统就有点儿困难了。
比如如果用户想要使用NFS做容器的持久化数据卷那么kubelet就需要在容器进行绑定挂载前在宿主机的指定目录上先挂载NFS的远程目录。
可是这时候问题来了。由于现在kubelet是运行在容器里的这就意味着它要做的这个“mount -F nfs”命令被隔离在了一个单独的Mount Namespace中。即kubelet做的挂载操作不能被“传播”到宿主机上。
对于这个问题有人说可以使用setns()系统调用在宿主机的Mount Namespace中执行这些挂载操作也有人说应该让Docker支持一个mnt=host的参数。
但是到目前为止在容器里运行kubelet依然没有很好的解决办法我也不推荐你用容器去部署Kubernetes项目。
正因为如此kubeadm选择了一种妥协方案
>
把kubelet直接运行在宿主机上然后使用容器部署其他的Kubernetes组件。
所以你使用kubeadm的第一步是在机器上手动安装kubeadm、kubelet和kubectl这三个二进制文件。当然kubeadm的作者已经为各个发行版的Linux准备好了安装包所以你只需要执行
```
$ apt-get install kubeadm
```
就可以了。
接下来你就可以使用“kubeadm init”部署Master节点了。
## kubeadm init的工作流程
当你执行kubeadm init指令后**kubeadm首先要做的是一系列的检查工作以确定这台机器可以用来部署Kubernetes**。这一步检查我们称为“Preflight Checks”它可以为你省掉很多后续的麻烦。
其实Preflight Checks包括了很多方面比如
- Linux内核的版本必须是否是3.10以上?
- Linux Cgroups模块是否可用
- 机器的hostname是否标准在Kubernetes项目里机器的名字以及一切存储在Etcd中的API对象都必须使用标准的DNS命名RFC 1123
- 用户安装的kubeadm和kubelet的版本是否匹配
- 机器上是不是已经安装了Kubernetes的二进制文件
- Kubernetes的工作端口10250/10251/10252端口是不是已经被占用
- ip、mount等Linux指令是否存在
- Docker是否已经安装
- ……
**在通过了Preflight Checks之后kubeadm要为你做的是生成Kubernetes对外提供服务所需的各种证书和对应的目录。**
Kubernetes对外提供服务时除非专门开启“不安全模式”否则都要通过HTTPS才能访问kube-apiserver。这就需要为Kubernetes集群配置好证书文件。
kubeadm为Kubernetes项目生成的证书文件都放在Master节点的/etc/kubernetes/pki目录下。在这个目录下最主要的证书文件是ca.crt和对应的私钥ca.key。
此外用户使用kubectl获取容器日志等streaming操作时需要通过kube-apiserver向kubelet发起请求这个连接也必须是安全的。kubeadm为这一步生成的是apiserver-kubelet-client.crt文件对应的私钥是apiserver-kubelet-client.key。
除此之外Kubernetes集群中还有Aggregate APIServer等特性也需要用到专门的证书这里我就不再一一列举了。需要指出的是你可以选择不让kubeadm为你生成这些证书而是拷贝现有的证书到如下证书的目录里
```
/etc/kubernetes/pki/ca.{crt,key}
```
这时kubeadm就会跳过证书生成的步骤把它完全交给用户处理。
**证书生成后kubeadm接下来会为其他组件生成访问kube-apiserver所需的配置文件**。这些文件的路径是:/etc/kubernetes/xxx.conf
```
ls /etc/kubernetes/
admin.conf controller-manager.conf kubelet.conf scheduler.conf
```
这些文件里面记录的是当前这个Master节点的服务器地址、监听端口、证书目录等信息。这样对应的客户端比如schedulerkubelet等可以直接加载相应的文件使用里面的信息与kube-apiserver建立安全连接。
**接下来kubeadm会为Master组件生成Pod配置文件**。我已经在上一篇文章中和你介绍过Kubernetes有三个Master组件kube-apiserver、kube-controller-manager、kube-scheduler而它们都会被使用Pod的方式部署起来。
你可能会有些疑问这时Kubernetes集群尚不存在难道kubeadm会直接执行docker run来启动这些容器吗
当然不是。
在Kubernetes中有一种特殊的容器启动方法叫做“Static Pod”。它允许你把要部署的Pod的YAML文件放在一个指定的目录里。这样当这台机器上的kubelet启动时它会自动检查这个目录加载所有的Pod YAML文件然后在这台机器上启动它们。
从这一点也可以看出kubelet在Kubernetes项目中的地位非常高在设计上它就是一个完全独立的组件而其他Master组件则更像是辅助性的系统容器。
在kubeadm中Master组件的YAML文件会被生成在/etc/kubernetes/manifests路径下。比如kube-apiserver.yaml
```
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: &quot;&quot;
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --authorization-mode=Node,RBAC
- --runtime-config=api/all=true
- --advertise-address=10.168.0.2
...
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
image: k8s.gcr.io/kube-apiserver-amd64:v1.11.1
imagePullPolicy: IfNotPresent
livenessProbe:
...
name: kube-apiserver
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /usr/share/ca-certificates
name: usr-share-ca-certificates
readOnly: true
...
hostNetwork: true
priorityClassName: system-cluster-critical
volumes:
- hostPath:
path: /etc/ca-certificates
type: DirectoryOrCreate
name: etc-ca-certificates
...
```
关于一个Pod的YAML文件怎么写、里面的字段如何解读我会在后续专门的文章中为你详细分析。在这里你只需要关注这样几个信息
<li>
这个Pod里只定义了一个容器它使用的镜像是`k8s.gcr.io/kube-apiserver-amd64:v1.11.1` 。这个镜像是Kubernetes官方维护的一个组件镜像。
</li>
<li>
这个容器的启动命令commands是kube-apiserver --authorization-mode=Node,RBAC …这样一句非常长的命令。其实它就是容器里kube-apiserver这个二进制文件再加上指定的配置参数而已。
</li>
<li>
如果你要修改一个已有集群的kube-apiserver的配置需要修改这个YAML文件。
</li>
<li>
这些组件的参数也可以在部署时指定,我很快就会讲到。
</li>
在这一步完成后kubeadm还会再生成一个Etcd的Pod YAML文件用来通过同样的Static Pod的方式启动Etcd。所以最后Master组件的Pod YAML文件如下所示
```
$ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
```
而一旦这些YAML文件出现在被kubelet监视的/etc/kubernetes/manifests目录下kubelet就会自动创建这些YAML文件中定义的Pod即Master组件的容器。
Master容器启动后kubeadm会通过检查localhost:6443/healthz这个Master组件的健康检查URL等待Master组件完全运行起来。
**然后kubeadm就会为集群生成一个bootstrap token**。在后面只要持有这个token任何一个安装了kubelet和kubadm的节点都可以通过kubeadm join加入到这个集群当中。
这个token的值和使用方法会在kubeadm init结束后被打印出来。
**在token生成之后kubeadm会将ca.crt等Master节点的重要信息通过ConfigMap的方式保存在Etcd当中供后续部署Node节点使用**。这个ConfigMap的名字是cluster-info。
**kubeadm init的最后一步就是安装默认插件**。Kubernetes默认kube-proxy和DNS这两个插件是必须安装的。它们分别用来提供整个集群的服务发现和DNS功能。其实这两个插件也只是两个容器镜像而已所以kubeadm只要用Kubernetes客户端创建两个Pod就可以了。
## kubeadm join的工作流程
这个流程其实非常简单kubeadm init生成bootstrap token之后你就可以在任意一台安装了kubelet和kubeadm的机器上执行kubeadm join了。
可是为什么执行kubeadm join需要这样一个token呢
因为任何一台机器想要成为Kubernetes集群中的一个节点就必须在集群的kube-apiserver上注册。可是要想跟apiserver打交道这台机器就必须要获取到相应的证书文件CA文件。可是为了能够一键安装我们就不能让用户去Master节点上手动拷贝这些文件。
所以kubeadm至少需要发起一次“不安全模式”的访问到kube-apiserver从而拿到保存在ConfigMap中的cluster-info它保存了APIServer的授权信息。而bootstrap token扮演的就是这个过程中的安全验证的角色。
只要有了cluster-info里的kube-apiserver的地址、端口、证书kubelet就可以以“安全模式”连接到apiserver上这样一个新的节点就部署完成了。
接下来,你只要在其他节点上重复这个指令就可以了。
## 配置kubeadm的部署参数
我在前面讲了kubeadm部署Kubernetes集群最关键的两个步骤kubeadm init和kubeadm join。相信你一定会有这样的疑问kubeadm确实简单易用可是我又该如何定制我的集群组件参数呢
比如我要指定kube-apiserver的启动参数该怎么办
在这里我强烈推荐你在使用kubeadm init部署Master节点时使用下面这条指令
```
$ kubeadm init --config kubeadm.yaml
```
这时你就可以给kubeadm提供一个YAML文件比如kubeadm.yaml它的内容如下所示我仅列举了主要部分
```
apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
api:
advertiseAddress: 192.168.0.102
bindPort: 6443
...
etcd:
local:
dataDir: /var/lib/etcd
image: &quot;&quot;
imageRepository: k8s.gcr.io
kubeProxy:
config:
bindAddress: 0.0.0.0
...
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
...
networking:
dnsDomain: cluster.local
podSubnet: &quot;&quot;
serviceSubnet: 10.96.0.0/12
nodeRegistration:
criSocket: /var/run/dockershim.sock
...
```
通过制定这样一个部署参数配置文件你就可以很方便地在这个文件里填写各种自定义的部署参数了。比如我现在要指定kube-apiserver的参数那么我只要在这个文件里加上这样一段信息
```
...
apiServerExtraArgs:
advertise-address: 192.168.0.103
anonymous-auth: false
enable-admission-plugins: AlwaysPullImages,DefaultStorageClass
audit-log-path: /home/johndoe/audit.log
```
然后kubeadm就会使用上面这些信息替换/etc/kubernetes/manifests/kube-apiserver.yaml里的command字段里的参数了。
而这个YAML文件提供的可配置项远不止这些。比如你还可以修改kubelet和kube-proxy的配置修改Kubernetes使用的基础镜像的URL默认的`k8s.gcr.io/xxx`镜像URL在国内访问是有困难的指定自己的证书文件指定特殊的容器运行时等等。这些配置项就留给你在后续实践中探索了。
## 总结
在今天的这次分享中我重点介绍了kubeadm这个部署工具的工作原理和使用方法。紧接着我会在下一篇文章中使用它一步步地部署一个完整的Kubernetes集群。
从今天的分享中你可以看到kubeadm的设计非常简洁。并且它在实现每一步部署功能时都在最大程度地重用Kubernetes已有的功能这也就使得我们在使用kubeadm部署Kubernetes项目时非常有“原生”的感觉一点都不会感到突兀。
而kubeadm的源代码直接就在kubernetes/cmd/kubeadm目录下是Kubernetes项目的一部分。其中app/phases文件夹下的代码对应的就是我在这篇文章中详细介绍的每一个具体步骤。
看到这里你可能会猜想kubeadm的作者一定是Google公司的某个“大神”吧。
实际上kubeadm几乎完全是一位高中生的作品。他叫Lucas Käldström芬兰人今年只有18岁。kubeadm是他17岁时用业余时间完成的一个社区项目。
所以说开源社区的魅力也在于此一个成功的开源项目总能够吸引到全世界最厉害的贡献者参与其中。尽管参与者的总体水平参差不齐而且频繁的开源活动又显得杂乱无章难以管控但一个有足够热度的社区最终的收敛方向却一定是代码越来越完善、Bug越来越少、功能越来越强大。
最后我再来回答一下我在今天这次分享开始提到的问题kubeadm能够用于生产环境吗
到目前为止2018年9月这个问题的答案是不能。
因为kubeadm目前最欠缺的是一键部署一个高可用的Kubernetes集群Etcd、Master组件都应该是多节点集群而不是现在这样的单点。这当然也正是kubeadm接下来发展的主要方向。
另一方面Lucas也正在积极地把kubeadm phases开放给用户用户可以更加自由地定制kubeadm的每一个部署步骤。这些举措都可以让这个项目更加完善我对它的发展走向也充满了信心。
当然,如果你有部署规模化生产环境的需求,我推荐使用[kops](https://github.com/kubernetes/kops)或者SaltStack这样更复杂的部署工具。但在本专栏接下来的讲解中我都会以kubeadm为依据进行讲述。
- 一方面作为Kubernetes项目的原生部署工具kubeadm对Kubernetes项目特性的使用和集成确实要比其他项目“技高一筹”非常值得我们学习和借鉴
- 另一方面kubeadm的部署方法不会涉及到太多的运维工作也不需要我们额外学习复杂的部署工具。而它部署的Kubernetes集群跟一个完全使用二进制文件搭建起来的集群几乎没有任何区别。
因此使用kubeadm去部署一个Kubernetes集群对于你理解Kubernetes组件的工作方式和架构最好不过了。
## 思考题
<li>
在Linux上为一个类似kube-apiserver的Web Server制作证书你知道可以用哪些工具实现吗
</li>
<li>
回忆一下我在前面文章中分享的Kubernetes架构你能够说出Kubernetes各个功能组件之间包含Etcd都有哪些建立连接或者调用的方式吗比如HTTP/HTTPS远程调用等等
</li>
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。

View File

@@ -0,0 +1,455 @@
<audio id="audio" title="11 | 从0到1搭建一个完整的Kubernetes集群" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/88/5d/88f0e653ae6279883eada707758f2a5d.mp3"></audio>
你好我是张磊。今天我和你分享的主题是从0到1搭建一个完整的Kubernetes集群。
不过,首先需要指出的是,本篇搭建指南是完全的手工操作,细节比较多,并且有些外部链接可能还会遇到特殊的“网络问题”。所以,对于只关心学习 Kubernetes 本身知识点、不太关注如何手工部署 Kubernetes 集群的同学,可以略过本节,直接使用 [MiniKube](https://github.com/kubernetes/minikube) 或者 [Kind](https://github.com/kubernetes-sigs/kind),来在本地启动简单的 Kubernetes 集群进行后面的学习即可。如果是使用 MiniKube 的话,阿里云还维护了一个[国内版的 MiniKube](https://github.com/AliyunContainerService/minikube),这对于在国内的同学来说会比较友好。
在上一篇文章中我介绍了kubeadm这个Kubernetes半官方管理工具的工作原理。既然kubeadm的初衷是让Kubernetes集群的部署不再让人头疼那么这篇文章我们就来使用它部署一个完整的Kubernetes集群吧。
>
<p>备注这里所说的“完整”指的是这个集群具备Kubernetes项目在GitHub上已经发布的所有功能并能够模拟生产环境的所有使用需求。但并不代表这个集群是生产级别可用的类似于高可用、授权、多租户、灾难备份等生产级别集群的功能暂时不在本篇文章的讨论范围。<br>
目前kubeadm的高可用部署[已经有了第一个发布](https://kubernetes.io/docs/setup/independent/high-availability/)。但是这个特性还没有GA生产可用所以包括了大量的手动工作跟我们所预期的一键部署还有一定距离。GA的日期预计是2018年底到2019年初。届时如果有机会我会再和你分享这部分内容。</p>
这次部署我不会依赖于任何公有云或私有云的能力而是完全在Bare-metal环境中完成。这样的部署经验会更有普适性。而在后续的讲解中如非特殊强调我也都会以本次搭建的这个集群为基础。
## 准备工作
首先,准备机器。最直接的办法,自然是到公有云上申请几个虚拟机。当然,如果条件允许的话,拿几台本地的物理服务器来组集群是最好不过了。这些机器只要满足如下几个条件即可:
<li>
满足安装Docker项目所需的要求比如64位的Linux操作系统、3.10及以上的内核版本;
</li>
<li>
x86或者ARM架构均可
</li>
<li>
机器之间网络互通,这是将来容器之间网络互通的前提;
</li>
<li>
有外网访问权限,因为需要拉取镜像;
</li>
<li>
能够访问到`gcr.io、quay.io`这两个docker registry因为有小部分镜像需要在这里拉取
</li>
<li>
单机可用资源建议2核CPU、8 GB内存或以上再小的话问题也不大但是能调度的Pod数量就比较有限了
</li>
<li>
30 GB或以上的可用磁盘空间这主要是留给Docker镜像和日志文件用的。
</li>
在本次部署中,我准备的机器配置如下:
<li>
2核CPU、 7.5 GB内存
</li>
<li>
30 GB磁盘
</li>
<li>
Ubuntu 16.04
</li>
<li>
内网互通;
</li>
<li>
外网访问权限不受限制。
</li>
>
备注在开始部署前我推荐你先花几分钟时间回忆一下Kubernetes的架构。
然后,我再和你介绍一下今天实践的目标:
<li>
在所有节点上安装Docker和kubeadm
</li>
<li>
部署Kubernetes Master
</li>
<li>
部署容器网络插件;
</li>
<li>
部署Kubernetes Worker
</li>
<li>
部署Dashboard可视化插件
</li>
<li>
部署容器存储插件。
</li>
好了,现在,就来开始这次集群部署之旅吧!
## 安装kubeadm和Docker
我在上一篇文章《 Kubernetes一键部署利器kubeadm》中已经介绍过kubeadm的基础用法它的一键安装非常方便我们只需要添加kubeadm的源然后直接使用apt-get安装即可具体流程如下所示
>
备注为了方便讲解我后续都会直接在root用户下进行操作。
```
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
$ cat &lt;&lt;EOF &gt; /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ apt-get update
$ apt-get install -y docker.io kubeadm
```
>
提示:如果 apt.kubernetes.io 因为网络问题访问不到,可以换成中科大的 Ubuntu 镜像源deb [http://mirrors.ustc.edu.cn/kubernetes/apt](http://mirrors.ustc.edu.cn/kubernetes/apt) kubernetes-xenial main。
在上述安装kubeadm的过程中kubeadm和kubelet、kubectl、kubernetes-cni这几个二进制文件都会被自动安装好。
另外这里我直接使用Ubuntu的docker.io的安装源原因是Docker公司每次发布的最新的Docker CE社区版产品往往还没有经过Kubernetes项目的验证可能会有兼容性方面的问题。
## 部署Kubernetes的Master节点
在上一篇文章中我已经介绍过kubeadm可以一键部署Master节点。不过在本篇文章中既然要部署一个“完整”的Kubernetes集群那我们不妨稍微提高一下难度通过配置文件来开启一些实验性功能。
所以这里我编写了一个给kubeadm用的YAML文件名叫kubeadm.yaml
```
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
controllerManagerExtraArgs:
horizontal-pod-autoscaler-use-rest-clients: &quot;true&quot;
horizontal-pod-autoscaler-sync-period: &quot;10s&quot;
node-monitor-grace-period: &quot;10s&quot;
apiServerExtraArgs:
runtime-config: &quot;api/all=true&quot;
kubernetesVersion: &quot;stable-1.11&quot;
```
这个配置中我给kube-controller-manager设置了
```
horizontal-pod-autoscaler-use-rest-clients: &quot;true&quot;
```
这意味着将来部署的kube-controller-manager能够使用自定义资源Custom Metrics进行自动水平扩展。这是我后面文章中会重点介绍的一个内容。
其中“stable-1.11”就是kubeadm帮我们部署的Kubernetes版本号Kubernetes release 1.11最新的稳定版在我的环境下它是v1.11.1。你也可以直接指定这个版本比如kubernetesVersion: “v1.11.1”。
然后,我们只需要执行一句指令:
```
$ kubeadm init --config kubeadm.yaml
```
就可以完成Kubernetes Master的部署了这个过程只需要几分钟。部署完成后kubeadm会生成一行指令
```
kubeadm join 10.168.0.2:6443 --token 00bwbx.uvnaa2ewjflwu1ry --discovery-token-ca-cert-hash sha256:00eb62a2a6020f94132e3fe1ab721349bbcd3e9b94da9654cfe15f2985ebd711
```
这个kubeadm join命令就是用来给这个Master节点添加更多工作节点Worker的命令。我们在后面部署Worker节点的时候马上会用到它所以找一个地方把这条命令记录下来。
此外kubeadm还会提示我们第一次使用Kubernetes集群所需要的配置命令
```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
而需要这些配置命令的原因是Kubernetes集群默认需要加密方式访问。所以这几条命令就是将刚刚部署生成的Kubernetes集群的安全配置文件保存到当前用户的.kube目录下kubectl默认会使用这个目录下的授权信息访问Kubernetes集群。
如果不这么做的话我们每次都需要通过export KUBECONFIG环境变量告诉kubectl这个安全配置文件的位置。
现在我们就可以使用kubectl get命令来查看当前唯一一个节点的状态了
```
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master NotReady master 1d v1.11.1
```
可以看到这个get指令输出的结果里Master节点的状态是NotReady这是为什么呢
在调试Kubernetes集群时最重要的手段就是用kubectl describe来查看这个节点Node对象的详细信息、状态和事件Event我们来试一下
```
$ kubectl describe node master
...
Conditions:
...
Ready False ... KubeletNotReady runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
```
通过kubectl describe指令的输出我们可以看到NodeNotReady的原因在于我们尚未部署任何网络插件。
另外我们还可以通过kubectl检查这个节点上各个系统Pod的状态其中kube-system是Kubernetes项目预留的系统Pod的工作空间Namepsace注意它并不是Linux Namespace它只是Kubernetes划分不同工作空间的单位
```
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-78fcdf6894-j9s52 0/1 Pending 0 1h
coredns-78fcdf6894-jm4wf 0/1 Pending 0 1h
etcd-master 1/1 Running 0 2s
kube-apiserver-master 1/1 Running 0 1s
kube-controller-manager-master 0/1 Pending 0 1s
kube-proxy-xbd47 1/1 NodeLost 0 1h
kube-scheduler-master 1/1 Running 0 1s
```
可以看到CoreDNS、kube-controller-manager等依赖于网络的Pod都处于Pending状态即调度失败。这当然是符合预期的因为这个Master节点的网络尚未就绪。
## 部署网络插件
在Kubernetes项目“一切皆容器”的设计理念指导下部署网络插件非常简单只需要执行一句kubectl apply指令以Weave为例
```
$ kubectl apply -f https://git.io/weave-kube-1.6
```
部署完成后我们可以通过kubectl get重新检查Pod的状态
```
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-78fcdf6894-j9s52 1/1 Running 0 1d
coredns-78fcdf6894-jm4wf 1/1 Running 0 1d
etcd-master 1/1 Running 0 9s
kube-apiserver-master 1/1 Running 0 9s
kube-controller-manager-master 1/1 Running 0 9s
kube-proxy-xbd47 1/1 Running 0 1d
kube-scheduler-master 1/1 Running 0 9s
weave-net-cmk27 2/2 Running 0 19s
```
可以看到所有的系统Pod都成功启动了而刚刚部署的Weave网络插件则在kube-system下面新建了一个名叫weave-net-cmk27的Pod一般来说这些Pod就是容器网络插件在每个节点上的控制组件。
Kubernetes支持容器网络插件使用的是一个名叫CNI的通用接口它也是当前容器网络的事实标准市面上的所有容器网络开源项目都可以通过CNI接入Kubernetes比如Flannel、Calico、Canal、Romana等等它们的部署方式也都是类似的“一键部署”。关于这些开源项目的实现细节和差异我会在后续的网络部分详细介绍。
至此Kubernetes的Master节点就部署完成了。如果你只需要一个单节点的Kubernetes现在你就可以使用了。不过在默认情况下Kubernetes的Master节点是不能运行用户Pod的所以还需要额外做一个小操作。在本篇的最后部分我会介绍到它。
## 部署Kubernetes的Worker节点
Kubernetes的Worker节点跟Master节点几乎是相同的它们运行着的都是一个kubelet组件。唯一的区别在于在kubeadm init的过程中kubelet启动后Master节点上还会自动运行kube-apiserver、kube-scheduler、kube-controller-manger这三个系统Pod。
所以相比之下部署Worker节点反而是最简单的只需要两步即可完成。
第一步在所有Worker节点上执行“安装kubeadm和Docker”一节的所有步骤。
第二步执行部署Master节点时生成的kubeadm join指令
```
$ kubeadm join 10.168.0.2:6443 --token 00bwbx.uvnaa2ewjflwu1ry --discovery-token-ca-cert-hash sha256:00eb62a2a6020f94132e3fe1ab721349bbcd3e9b94da9654cfe15f2985ebd711
```
## 通过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节点上就会增加一个键值对格式的Taintfoo=bar:NoSchedule。其中值里面的NoSchedule意味着这个Taint只会在调度新Pod时产生作用而不会影响已经在node1上运行的Pod哪怕它们没有Toleration。
那么Pod又如何声明Toleration呢
我们只要在Pod的.yaml文件中的spec部分加入tolerations字段即可
```
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: &quot;foo&quot;
operator: &quot;Equal&quot;
value: &quot;bar&quot;
effect: &quot;NoSchedule&quot;
```
这个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: &quot;foo&quot;
operator: &quot;Exists&quot;
effect: &quot;NoSchedule&quot;
```
当然如果你就是想要一个单节点的Kubernetes删除这个Taint才是正确的选择
```
$ kubectl taint nodes --all node-role.kubernetes.io/master-
```
如上所示,我们在“`node-role.kubernetes.io/master`”这个键后面加上了一个短横线“-”,这个格式就意味着移除所有以“`node-role.kubernetes.io/master`”为键的Taint。
到了这一步一个基本完整的Kubernetes集群就部署完毕了。是不是很简单呢
有了kubeadm这样的原生管理工具Kubernetes的部署已经被大大简化。更重要的是像证书、授权、各个组件的配置等部署中最麻烦的操作kubeadm都已经帮你完成了。
接下来我们再在这个Kubernetes集群上安装一些其他的辅助插件比如Dashboard和存储插件。
## 部署Dashboard可视化插件
在Kubernetes社区中有一个很受欢迎的Dashboard项目它可以给用户提供一个可视化的Web界面来查看当前集群的各种信息。毫不意外它的部署也相当简单
```
$ kubectl apply -f
$ $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc6/aio/deploy/recommended.yaml
```
部署完成之后我们就可以查看Dashboard对应的Pod的状态了
```
$ kubectl get pods -n kube-system
kubernetes-dashboard-6948bdb78-f67xk 1/1 Running 0 1m
```
需要注意的是由于Dashboard是一个Web Server很多人经常会在自己的公有云上无意地暴露Dashboard的端口从而造成安全隐患。所以1.7版本之后的Dashboard项目部署完成后默认只能通过Proxy的方式在本地访问。具体的操作你可以查看Dashboard项目的[官方文档](https://github.com/kubernetes/dashboard)。
而如果你想从集群外访问这个Dashboard的话就需要用到Ingress我会在后面的文章中专门介绍这部分内容。
## 部署容器存储插件
接下来让我们完成这个Kubernetes集群的最后一块拼图容器持久化存储。
我在前面介绍容器原理时已经提到过很多时候我们需要用数据卷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/common.yaml
$ 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项目会将自己的Pod放置在由它自己管理的两个Namespace当中
```
$ kubectl get pods -n rook-ceph-system
NAME READY STATUS RESTARTS AGE
rook-ceph-agent-7cv62 1/1 Running 0 15s
rook-ceph-operator-78d498c68c-7fj72 1/1 Running 0 44s
rook-discover-2ctcv 1/1 Running 0 15s
$ kubectl get pods -n rook-ceph
NAME READY STATUS RESTARTS AGE
rook-ceph-mon0-kxnzh 1/1 Running 0 13s
rook-ceph-mon1-7dn2t 1/1 Running 0 2s
```
这样一个基于Rook持久化存储集群就以容器的方式运行起来了而接下来在Kubernetes项目上创建的所有Pod就能够通过Persistent VolumePV和Persistent Volume ClaimPVC的方式在容器里挂载由Ceph提供的数据卷了。
而Rook项目则会负责这些数据卷的生命周期管理、灾难备份等运维工作。关于这些容器持久化存储的知识我会在后续章节中专门讲解。
这时候你可能会有个疑问为什么我要选择Rook项目呢
其实,是因为这个项目很有前途。
如果你去研究一下Rook项目的实现就会发现它巧妙地依赖了Kubernetes提供的编排能力合理的使用了很多诸如Operator、CRD等重要的扩展特性这些特性我都会在后面的文章中逐一讲解到。这使得Rook项目成为了目前社区中基于Kubernetes API构建的最完善也最成熟的容器存储插件。我相信这样的发展路线很快就会得到整个社区的推崇。
>
备注其实在很多时候大家说的所谓“云原生”就是“Kubernetes原生”的意思。而像Rook、Istio这样的项目正是贯彻这个思路的典范。在我们后面讲解了声明式API之后相信你对这些项目的设计思想会有更深刻的体会。
## 总结
在本篇文章中我们完全从0开始在Bare-metal环境下使用kubeadm工具部署了一个完整的Kubernetes集群这个集群有一个Master节点和多个Worker节点使用Weave作为容器网络插件使用Rook作为容器持久化存储插件使用Dashboard插件提供了可视化的Web界面。
这个集群,也将会是我进行后续讲解所依赖的集群环境,并且在后面的讲解中,我还会给它安装更多的插件,添加更多的新能力。
另外,这个集群的部署过程并不像传说中那么繁琐,这主要得益于:
<li>
kubeadm项目大大简化了部署Kubernetes的准备工作尤其是配置文件、证书、二进制文件的准备和制作以及集群版本管理等操作都被kubeadm接管了。
</li>
<li>
Kubernetes本身“一切皆容器”的设计思想加上良好的可扩展机制使得插件的部署非常简便。
</li>
上述思想也是开发和使用Kubernetes的重要指导思想基于Kubernetes开展工作时你一定要优先考虑这两个问题
<li>
我的工作是不是可以容器化?
</li>
<li>
我的工作是不是可以借助Kubernetes API和可扩展机制来完成
</li>
而一旦这项工作能够基于Kubernetes实现容器化就很有可能像上面的部署过程一样大幅简化原本复杂的运维工作。对于时间宝贵的技术人员来说这个变化的重要性是不言而喻的。
## 思考题
<li>
你是否使用其他工具部署过Kubernetes项目经历如何
</li>
<li>
你是否知道Kubernetes项目当前v1.11)能够有效管理的集群规模是多少个节点?你在生产环境中希望部署或者正在部署的集群规模又是多少个节点呢?
</li>
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。

View File

@@ -0,0 +1,350 @@
<audio id="audio" title="12 | 牛刀小试:我的第一个容器化应用" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/06/5a/068473ddafac8ef5798b843cddccf65a.mp3"></audio>
你好,我是张磊。今天我和你分享的主题是:牛刀小试之我的第一个容器化应用。
在上一篇文章《从0到1搭建一个完整的Kubernetes集群》中我和你一起部署了一套完整的Kubernetes集群。这个集群虽然离生产环境的要求还有一定差距比如没有一键高可用部署但也可以当作是一个准生产级别的Kubernetes集群了。
而在这篇文章中我们就来扮演一个应用开发者的角色使用这个Kubernetes集群发布第一个容器化应用。
在开始实践之前我先给你讲解一下Kubernetes里面与开发者关系最密切的几个概念。
作为一个应用开发者,你首先要做的,是制作容器的镜像。这一部分内容,我已经在容器基础部分[《白话容器基础(三):深入理解容器镜像》](https://time.geekbang.org/column/article/17921)重点讲解过了。
而有了容器镜像之后你需要按照Kubernetes项目的规范和要求将你的镜像组织为它能够“认识”的方式然后提交上去。
那么什么才是Kubernetes项目能“认识”的方式呢
这就是使用Kubernetes的必备技能编写配置文件。
>
备注这些配置文件可以是YAML或者JSON格式的。为方便阅读与理解在后面的讲解中我会统一使用YAML文件来指代它们。
Kubernetes跟Docker等很多项目最大的不同就在于它不推荐你使用命令行的方式直接运行容器虽然Kubernetes项目也支持这种方式比如kubectl run而是希望你用YAML文件的方式把容器的定义、参数、配置统统记录在一个YAML文件中然后用这样一句指令把它运行起来
```
$ kubectl create -f 我的配置文件
```
这么做最直接的好处是你会有一个文件能记录下Kubernetes到底“run”了什么。比如下面这个例子
```
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
```
像这样的一个YAML文件对应到Kubernetes中就是一个API ObjectAPI对象。当你为这个对象的各个字段填好值并提交给Kubernetes之后Kubernetes就会负责创建出这些对象所定义的容器或者其他类型的API资源。
可以看到这个YAML文件中的Kind字段指定了这个API对象的类型Type是一个Deployment。
所谓Deployment是一个定义多副本应用即多个副本Pod的对象我在前面的文章中也是第9篇文章《从容器到容器云谈谈Kubernetes的本质》曾经简单提到过它的用法。此外Deployment还负责在Pod定义发生变化时对每个副本进行滚动更新Rolling Update
在上面这个YAML文件中我给它定义的Pod副本个数(spec.replicas)是2。
而这些Pod具体的又长什么样子呢
为此我定义了一个Pod模版spec.template这个模版描述了我想要创建的Pod的细节。在上面的例子里这个Pod里只有一个容器这个容器的镜像spec.containers.image是nginx:1.7.9这个容器监听端口containerPort是80。
关于Pod的设计和用法我已经在第9篇文章[《从容器到容器云谈谈Kubernetes的本质》](https://time.geekbang.org/column/article/23132)中简单的介绍过。而在这里,你需要记住这样一句话:
>
Pod就是Kubernetes世界里的“应用”而一个应用可以由多个容器组成。
需要注意的是像这样使用一种API对象Deployment管理另一种API对象Pod的方法在Kubernetes中叫作“控制器”模式controller pattern。在我们的例子中Deployment扮演的正是Pod的控制器的角色。关于Pod和控制器模式的更多细节我会在后续编排部分做进一步讲解。
你可能还注意到这样的每一个API对象都有一个叫作Metadata的字段这个字段就是API对象的“标识”即元数据它也是我们从Kubernetes里找到这个对象的主要依据。这其中最主要使用到的字段是Labels。
顾名思义Labels就是一组key-value格式的标签。而像Deployment这样的控制器对象就可以通过这个Labels字段从Kubernetes中过滤出它所关心的被控制对象。
比如在上面这个YAML文件中Deployment会把所有正在运行的、携带“app: nginx”标签的Pod识别为被管理的对象并确保这些Pod的总数严格等于两个。
而这个过滤规则的定义是在Deployment的“spec.selector.matchLabels”字段。我们一般称之为Label Selector。
另外在Metadata中还有一个与Labels格式、层级完全相同的字段叫Annotations它专门用来携带key-value格式的内部信息。所谓内部信息指的是对这些信息感兴趣的是Kubernetes组件本身而不是用户。所以大多数Annotations都是在Kubernetes运行过程中被自动加在这个API对象上。
一个Kubernetes的API对象的定义大多可以分为Metadata和Spec两个部分。前者存放的是这个对象的元数据对所有API对象来说这一部分的字段和格式基本上是一样的而后者存放的则是属于这个对象独有的定义用来描述它所要表达的功能。
在了解了上述Kubernetes配置文件的基本知识之后我们现在就可以把这个YAML文件“运行”起来。正如前所述你可以使用kubectl create指令完成这个操作
```
$ kubectl create -f nginx-deployment.yaml
```
然后通过kubectl get命令检查这个YAML运行起来的状态是不是与我们预期的一致
```
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-67594d6bf6-9gdvr 1/1 Running 0 10m
nginx-deployment-67594d6bf6-v6j7w 1/1 Running 0 10m
```
kubectl get指令的作用就是从Kubernetes里面获取GET指定的API对象。可以看到在这里我还加上了一个-l参数即获取所有匹配app: nginx标签的Pod。需要注意的是**在命令行中所有key-value格式的参数都使用“=”而非“:”表示。**
从这条指令返回的结果中我们可以看到现在有两个Pod处于Running状态也就意味着我们这个Deployment所管理的Pod都处于预期的状态。
此外, 你还可以使用kubectl describe命令查看一个API对象的细节比如
```
$ kubectl describe pod nginx-deployment-67594d6bf6-9gdvr
Name: nginx-deployment-67594d6bf6-9gdvr
Namespace: default
Priority: 0
PriorityClassName: &lt;none&gt;
Node: node-1/10.168.0.3
Start Time: Thu, 16 Aug 2018 08:48:42 +0000
Labels: app=nginx
pod-template-hash=2315082692
Annotations: &lt;none&gt;
Status: Running
IP: 10.32.0.23
Controlled By: ReplicaSet/nginx-deployment-67594d6bf6
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned default/nginx-deployment-67594d6bf6-9gdvr to node-1
Normal Pulling 25s kubelet, node-1 pulling image &quot;nginx:1.7.9&quot;
Normal Pulled 17s kubelet, node-1 Successfully pulled image &quot;nginx:1.7.9&quot;
Normal Created 17s kubelet, node-1 Created container
Normal Started 17s kubelet, node-1 Started container
```
在kubectl describe命令返回的结果中你可以清楚地看到这个Pod的详细信息比如它的IP地址等等。其中有一个部分值得你特别关注它就是**Events事件。**
在Kubernetes执行的过程中对API对象的所有重要操作都会被记录在这个对象的Events里并且显示在kubectl describe指令返回的结果中。
比如对于这个Pod我们可以看到它被创建之后被调度器调度Successfully assigned到了node-1拉取了指定的镜像pulling image然后启动了Pod里定义的容器Started container
所以这个部分正是我们将来进行Debug的重要依据。**如果有异常发生你一定要第一时间查看这些Events**,往往可以看到非常详细的错误信息。
接下来如果我们要对这个Nginx服务进行升级把它的镜像版本从1.7.9升级为1.8,要怎么做呢?
很简单我们只要修改这个YAML文件即可。
```
...
spec:
containers:
- name: nginx
image: nginx:1.8 #这里被从1.7.9修改为1.8
ports:
- containerPort: 80
```
可是这个修改目前只发生在本地如何让这个更新在Kubernetes里也生效呢
我们可以使用kubectl replace指令来完成这个更新
```
$ kubectl replace -f nginx-deployment.yaml
```
不过在本专栏里我推荐你使用kubectl apply命令来统一进行Kubernetes对象的创建和更新操作具体做法如下所示
```
$ kubectl apply -f nginx-deployment.yaml
# 修改nginx-deployment.yaml的内容
$ kubectl apply -f nginx-deployment.yaml
```
这样的操作方法是Kubernetes“声明式API”所推荐的使用方法。也就是说作为用户你不必关心当前的操作是创建还是更新你执行的命令始终是kubectl apply而Kubernetes则会根据YAML文件的内容变化自动进行具体的处理。
而这个流程的好处是它有助于帮助开发和运维人员围绕着可以版本化管理的YAML文件而不是“行踪不定”的命令行进行协作从而大大降低开发人员和运维人员之间的沟通成本。
举个例子一位开发人员开发好一个应用制作好了容器镜像。那么他就可以在应用的发布目录里附带上一个Deployment的YAML文件。
而运维人员拿到这个应用的发布目录后就可以直接用这个YAML文件执行kubectl apply操作把它运行起来。
这时候如果开发人员修改了应用生成了新的发布内容那么这个YAML文件也就需要被修改并且成为这次变更的一部分。
而接下来运维人员可以使用git diff命令查看到这个YAML文件本身的变化然后继续用kubectl apply命令更新这个应用。
所以说如果通过容器镜像我们能够保证应用本身在开发与部署环境里的一致性的话那么现在Kubernetes项目通过这些YAML文件就保证了应用的“部署参数”在开发与部署环境中的一致性。
**而当应用本身发生变化时开发人员和运维人员可以依靠容器镜像来进行同步当应用部署参数发生变化时这些YAML文件就是他们相互沟通和信任的媒介。**
以上就是Kubernetes发布应用的最基本操作了。
接下来我们再在这个Deployment中尝试声明一个Volume。
在Kubernetes中Volume是属于Pod对象的一部分。所以我们就需要修改这个YAML文件里的template.spec字段如下所示
```
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
volumeMounts:
- mountPath: &quot;/usr/share/nginx/html&quot;
name: nginx-vol
volumes:
- name: nginx-vol
emptyDir: {}
```
可以看到我们在Deployment的Pod模板部分添加了一个volumes字段定义了这个Pod声明的所有Volume。它的名字叫作nginx-vol类型是emptyDir。
那什么是emptyDir类型呢
它其实就等同于我们之前讲过的Docker的隐式Volume参数不显式声明宿主机目录的Volume。所以Kubernetes也会在宿主机上创建一个临时目录这个目录将来就会被绑定挂载到容器所声明的Volume目录上。
>
备注不难看到Kubernetes的emptyDir类型只是把Kubernetes创建的临时目录作为Volume的宿主机目录交给了Docker。这么做的原因是Kubernetes不想依赖Docker自己创建的那个_data目录。
而Pod中的容器使用的是volumeMounts字段来声明自己要挂载哪个Volume并通过mountPath字段来定义容器内的Volume目录比如/usr/share/nginx/html。
当然Kubernetes也提供了显式的Volume定义它叫作hostPath。比如下面的这个YAML文件
```
...
volumes:
- name: nginx-vol
hostPath:
path: &quot; /var/data&quot;
```
这样容器Volume挂载的宿主机目录就变成了/var/data。
在上述修改完成后我们还是使用kubectl apply指令更新这个Deployment:
```
$ kubectl apply -f nginx-deployment.yaml
```
接下来你可以通过kubectl get指令查看两个Pod被逐一更新的过程
```
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5c678cfb6d-v5dlh 0/1 ContainerCreating 0 4s
nginx-deployment-67594d6bf6-9gdvr 1/1 Running 0 10m
nginx-deployment-67594d6bf6-v6j7w 1/1 Running 0 10m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5c678cfb6d-lg9lw 1/1 Running 0 8s
nginx-deployment-5c678cfb6d-v5dlh 1/1 Running 0 19s
```
从返回结果中我们可以看到新旧两个Pod被交替创建、删除最后剩下的就是新版本的Pod。这个滚动更新的过程我也会在后续进行详细的讲解。
然后你可以使用kubectl describe查看一下最新的Pod就会发现Volume的信息已经出现在了Container描述部分
```
...
Containers:
nginx:
Container ID: docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343
Image: nginx:1.8
...
Environment: &lt;none&gt;
Mounts:
/usr/share/nginx/html from nginx-vol (rw)
...
Volumes:
nginx-vol:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
```
>
备注作为一个完整的容器化平台项目Kubernetes为我们提供的Volume类型远远不止这些在容器存储章节里我将会为你详细介绍这部分内容。
最后你还可以使用kubectl exec指令进入到这个Pod当中即容器的Namespace中查看这个Volume目录
```
$ kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash
# ls /usr/share/nginx/html
```
此外你想要从Kubernetes集群中删除这个Nginx Deployment的话直接执行
```
$ kubectl delete -f nginx-deployment.yaml
```
就可以了。
## 总结
在今天的分享中我通过一个小案例和你近距离体验了Kubernetes的使用方法。
可以看到Kubernetes推荐的使用方式是用一个YAML文件来描述你所要部署的API对象。然后统一使用kubectl apply命令完成对这个对象的创建和更新操作。
而Kubernetes里“最小”的API对象是Pod。Pod可以等价为一个应用所以Pod可以由多个紧密协作的容器组成。
在Kubernetes中我们经常会看到它通过一种API对象来管理另一种API对象比如Deployment和Pod之间的关系而由于Pod是“最小”的对象所以它往往都是被其他对象控制的。这种组合方式正是Kubernetes进行容器编排的重要模式。
而像这样的Kubernetes API对象往往由Metadata和Spec两部分组成其中Metadata里的Labels字段是Kubernetes过滤对象的主要手段。
在这些字段里面容器想要使用的数据卷也就是Volume正是Pod的Spec字段的一部分。而Pod里的每个容器则需要显式的声明自己要挂载哪个Volume。
上面这些基于YAML文件的容器管理方式跟Docker、Mesos的使用习惯都是不一样的而从docker run这样的命令行操作向kubectl apply YAML文件这样的声明式API的转变是每一个容器技术学习者必须要跨过的第一道门槛。
所以如果你想要快速熟悉Kubernetes请按照下面的流程进行练习
- 首先在本地通过Docker测试代码制作镜像
- 然后选择合适的Kubernetes API对象编写对应YAML文件比如PodDeployment
- 最后在Kubernetes上部署这个YAML文件。
更重要的是在部署到Kubernetes之后接下来的所有操作要么通过kubectl来执行要么通过修改YAML文件来实现**就尽量不要再碰Docker的命令行了**。
## 思考题
在实际使用Kubernetes的过程中相比于编写一个单独的Pod的YAML文件我一定会推荐你使用一个replicas=1的Deployment。请问这两者有什么区别呢
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。