Kubernetes部署教程


对于Kubernetes部署,有两种基本的方法:使用一条条的kubectl执行命令,或者编写声明清单,再使用kubectl应用工具执行。前者有利于学习和与Kubernetes的交互实验(类似于编程语言的REPL);后者适合于可重现重用的快速部署。例如在生产环境中,您可能仍然会使用一条条的kubectl命令来做调试。

前提条件

本教程假设您已经具有了可访问的Kubernetes集群环境。现在,您可以轻松地在您的机器上运行一个单节点集群,或者在您熟悉的云环境中创建一个多节点集群。并确定您的机器上已经安装并正确配置了kubectl。让如下命令可以成功执行:
kubectl cluster-info

还需确认您在缺省命名空间内的权限是否足够(可能与缺省命名空间不同)。edit角色权限能够满足实验需求。如果您为本教程创建了一个集群,那么您可能就是管理员。或者让其他人为您准备好环境。

备注:如果您不想用公共的镜像文件,而想自己构建并推送一个自定义的境像。那我们假定您和您的集群是可以访问容器镜像注册器的。同样的,也可以让其他人为您准备好此环境。业界协作比较好的组合环境包括GKE与GCR、AKS与ACR等。另外,DockerHub和Quay也是非常流行的镜像注册器。如果不打算公开自己的容器镜像,则需要在私有的命名空间中,给默认服务帐户配置拉取镜像密钥。

构建和推送

不管是采用指令式部署还是声明式部署,都需要有一个容器镜像。(如果您使用现成的镜像,如Nginx镜像,可跳过此部分)。本教程中的一些步骤与后续构建的应用程序有直接关联。同时,也可帮助您学习如何封装应用程序,所以建议您不要忽略此部分。

应教学目的需要,我们将从一个简单的Web应用源代码开始教程。下面是Node.js文档中的一个案例应用代码(或采用您所喜欢的编程语言编写一个类似的应用程序)。将以下的代码复制到一个名为app.js的文件中,并存放在一个空的文件夹下。
*// app.js
const http = require('http');
const os = require('os');
const ip = '0.0.0.0';
const port = 3000;
const hostname = os.hostname();
const whoami = process.env['WHOAMI'] || 'Anonymous';
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`Hi, I’m ${whoami}, from ${hostname}.\n`);
});
server.listen(port, ip, () => {
console.log(`Server running at http://${ip}:${port}/`);
});*

我们对上述案例代码做了一些调整:
  • 将服务器本机IP定义为0.0.0.0而不是127.0.0.1。因为后者仅仅适用于IP回路寻址。而这段程序需监听来自一个集群IP的请求(由0.0.0.0捕获请求)。
  • 将“Hello World”消息分拆为2个变量:主机名,提供请求响应的一个副本节点的标识;WHOAMI环境变量,在部署时设定,默认值为“Anonymous”。


如果您已经安装了Node.js,那可以通过以下命令进行代码的本地测试:
node app.js # 打开链接  http://localhost:3000测试

备注:如果您在自己的Kubernetes集群环境进行测试,无需担心TLS终端和授权问题,因为这个应用可以在集群的边界运行,例如Ambassador。如果您采用Zero-trust,这段代码可以运行在Istio之类服务网格集群的微服务内。

接下来将应用打包成一个Docker镜像。复制如下代码,保存为Dockerfile的文件:
# Dockerfile
FROM node:8
COPY app.js .
ENTRYPOINT [“node”, “app.js”]

在文件所在目录下,执行命令:
docker build -t myrepo:mytag .

请根据您的容器镜像注册器,替换 myrepo。例如:
采用GCR,就替换为gcr.io/project-name/image-name,采用默认的DockerHub,就替换为user-name/image-name,用非latest的任何词语,来替代mytag。tag不能重复,如果您是和多人一起学习本课程时,请确保这点。最后的那个点不能遗漏,它表示用当前目录环境构建上下文。

最后,将镜像推到您的镜像库(也就是Kubernetes拉取镜像的地方):
docker push myrepo:mytag

命令式的配置方法

RUN

部署Kubernetes最简捷的方式就是采用kubectl run命令。
kubectl run myapp --image myrepo:mytag --replicas 2

如果是多人共享一个命名空间,请采用唯一的名称替代myapp,采用上述步骤中的指定的库地址和名称(或Nginx)替代myrepo:mytag。

这个命令,看起来与本地启动容器的docker run命令有些相似, 但相似的地方也就这些而已。(再后面的交互图中也有体现)。

本质上Kubectl将用户的命令语句转换成一个Kubernetes的Deployment对象的声明。而一个Deployment是一个能滚动升级的高级API。
  1. Kubectl将Deployment对象发送给运行在Kubernetes集群内的API服务器—— kube-apiserver。
  2. kube-apiserver将Deployment信息保存到集群内分布式键值对存储etcd中,etcd为Kubectl请求提供响应。
  3. Kubernetes负责监控Deployment及其他事件的控制器管理器kube-controller-manager,将为Deployment创建一个ReplicasSet副本,并将其发送到kube-apiserver。一个ReplicaSet就是一个Deployment的版本。在滚动更新时,将创建一个新的ReplicaSet,并逐步扩展到预先配置的期望副本数,而旧的ReplicasSet数量逐步变为零。这整个过程都是采用异步机制。
  4. kube-apiserver将ReplicaSet信息保存到etcd中。
  5. kube-controller-manager同样以异步的方式为ReplicaSet创建2个或以上的Pod,并将其发送给kube-apiserver。Pod是Kubernetes的基本单元,是搭载1或多个共享Linux cgroup和命名空间的容器环境。
  6. kube-apiserver将Pod信息保存到etcd中。
  7. 负责监视Pod事件的Kubernetes调度器kube-scheduler,将逐步更新每个Pod,为其分配到某个节点,并将更新的信息发送回kube-apiserver。
  8. kube-apiserver在etcd中更新Pod状态。
  9. 最后,节点上运行的kubelet会实时监控Pod,并在Pod启动了容器。


备注:容器,调度器和kubelet同样会将自身的状态信息发送给API Server。总而言之,我们可以将Kubernetes理解成一个不断循环的CRUD API。如下是上述过程的交互图:
图片_2.png

Get,Describe

kubectl run创建一个Deployment,然后又衍生创建ReplicaSet和Pod。但它们都运行在哪里呢?我们可以采用kubectl get命令去列出您默认命名空间下所有的 Deployments,ReplicaSet和Pod:
kubectl get deployments # plural or singular, or deploy for short
kubectl get replicasets # or rs
kubectl get pods # or po

在对象后面加上对象名可以列出单个的对象。如:
kubectl get deployment myapp

导出etcd中的对象状态信息--output option (or -o):
kubectl get deployment myapp -o yaml

要获得更多的细节信息,包括与此对象相关的近期事件信息。可以采用kubectl describe命令:
kubectl describe deployment myapp

上述命令与参数同样适用于ReplicaSet 和Pod。

Label

标签的作用非常大,一个标签是一个字符串的健值对。Kubernetes所有的对象都可以被标签,并且可以按标签做成选择器。通过kubectl run命令加run=myapp的方式可以自动定位Deployment,ReplicaSet和Pod。您可以在YAML和描述中看到对象的标签,也可以通过--show-labels参数,将标签输出。命令如下:
kubectl get deployments --show-labels
kubectl get replicasets --show-labels
kubectl get pods --show-labels

可以采用 --label-columns ( -L) 参数将标签以列的方式显示。如:
kubectl get replicasets -L run

一种重要用法是采用--selector (-l)参数进行标签过滤。如:
kubectl get pods -l run=myapp

还可以手动添加新的标签。如:
kubectl label deployment myapp foo=bar

Delete

Kubernetes API本质上是声明性的,这意味着控制器总是在比较对象状态和所期望的状态。因此,如果我们删除一个Pod,ReplicaSet控制器将创建一个新的Pod,以维护所期望的副本数。我们通过如下测试验证一下:
kubectl delete pods -l run=myapp
# 等待一会儿后再运行
kubectl get pods -l run=myapp

我们会发现Pod总数没变,但通过随机后缀发现是创建了新的Pod。这个机制同样适用于ReplicaSet。如果我们删除一个,控制器也将自动创建一个新的。但如果我们将Deployment删除了,那就表示删除这个应用,这时候就不会再有新的创建动作。

Port-Forward

到目前为止,我们已经看到了Kubernetes为响应kubectl run而创建的Pod对象。但Pod之所以随时可以响应,是因为容器中有进程正在运行着。我们可以定义一个活性探针(Liveness probe)来验证。但是现在,我们先通过手动操作来让应用程序正常工作。即通过kubectl port-forward命令,让API服务将一个本地端口代理成Pod的端口(如果本地和API服务器的网络是互联网的话,需要采用TLS加密)。kubectl port-forward命令非常灵活,如果您同时提供了Pod名和端口号,kubectl就直接按提供的值进行映射。但由于Pod名通常是自动生成的,名字带有随机数,所以方便起见,建议提供的名字是类型/名字的键值对格式。如:
kubectl port-forward deployment/myapp 3000

浏览器打开如下链接,您将看到“Hello World”的消息。

http://localhost:3000/

由于上述方式只能映射一个Pod,所以当您重新加载后,不能修改主机名。同时请注意,采用kubectl run命令时,我们不能通过–env参数修改WHOAMI 环境变量。只能通过kubectl set 或kubectl patch命令才行的(在稍后介绍)。

如果本地端口与Pod端口不一致的话,需在命令内注明,如:
kubectl port-forward deployment/myapp 5000:3000

上述所配置的端口代理可以通过Ctrl+C停止。如果代理连接不活跃的话,系统也会被自动关闭这个代理连接。

Expose

port-forward可以让一个应用运行起来,但不支持多个应用同时代理。而且,我们用到的Pod IP和DNS记录,会因为Pod生命周期很短,而导致不断变化。因此,我们需要一种方法来与Deployment或其他类型的Pod组(而非单个Pod)保持通信。这就是服务发现。

Kubernetes的Service对象就是将流量路由到一组Pod上,而这组Pod与Service的标签选择器相匹配。如果多个Pod与标签选择器匹配,那它们都将被侦听,并接收到所路由的流量。如果发布一个Deployment,我们可以简单地将它的标签选择器(run=app)作为Service的标签选择器。

The kubectl expose命令能够自动根据Deployment、ReplicaSet、其他的Service或单个Pod来创建Service。它会自动从给定对象中查找标签选择器、服务端口和目标Pod端口,除非有特定选项指定。如:
kubectl expose deployment myapp --port 80 
# 因为本地设备端口不能用,采用标准的HTTP端口

确认一下所创建Service 的信息:
kubectl get service # or svc

可在Service描述中或所控制的Endpoints中,看到监听Pod的IP,这方法可以用于网络问题的诊断。
kubectl describe service myapp
kubectl get endpoints myapp

如下是启动一个带有交互式终端的临时Pod,来实际调用服务:
kubectl run mytest -it --rm --image alpine # Alpine很轻量
# inside mytest:
apk add curl
curl http://myapp # “Hello World…”
exit

备注:myapp的Deployment需提前创建。
  • -t或–tty,分配一个TTY终端
  • -i或–stdin保持stdin打开
  • --rm退出时删除Deployment


Logs

如应用程序有问题,通常要检查日志。容器日志通常是由容器运行时(Docker守护进程)存储在节点上。默认情况下,当日志文件超过10MB时,日志会自动循环覆盖。可以配置一个日志代理,将日志推送到后端的持久存储上。请记住一点,kubectl logs命令只能看到节点上上的日志。

可以通过Pod名获取一个Pod的日志,也可以通过类型/名字获取一组Pod的日志。如:
kubectl logs deployment/myapp

在我们的教学中,日志较少,但实际生产应用的日志会详细且多很多。尤其是出问题的时候。可以通行参数,按日志量—since (1m),日志时间--since-time (2018–11–01T16:30:00)或尾部记录数 --tail (20)来设定输出结果。其他的参数选项还包括 –follow(-f),--previous(容器持续崩溃),--timestamps(应用没有记录时间戳)。最后将日志输出到文件和进行您所需的分析。如:
kubectl logs deployment/myapp --since 5m > log.txt
grep error log.txt
# more grep

Exec,Copy

kubectl还提供了一些其他的工具来帮助我们调试正在运行的容器。kubectl exec是在容器中执行命令,kubectl cp在容器之间复制文件和目录。这两个命令使用显式的Pod名称,而不是Deployment名。如下是用--output jsonpath在shell变量中存储Pod名称的用法:
POD_NAME=$(kubectl get pods -l run=myapp -o 
jsonpath={.items[0].metadata.name})

我们可以将这个变量用到其他命令内:
kubectl exec $POD_NAME -it sh # 在容器内打开一个交互式shell。
# 容器内的执行段样本:
node --version
echo $WHOAMI
exit
# 返回到本地机器
kubectl cp $POD_NAME:app.js remote-app.js # 用于理解容器内运行的样本

Set,Scale,Patch

如前面提到的,我们需要用kubectl set或kubectl patch来设置WHOAMI环境变量。在此过程中,我们还同步学习如何使用kubectl get -watch(或-w)查看资源更改,并实时观察多个对象的滚动更新:
kubectl get deployment myapp -w
# 在第二个终端:
kubectl get replicasets -w -l run=myapp
# 在第三个终端:
kubectl get pods -w -l run=myapp
# 在第四个终端:
kubectl set env deployment/myapp WHOAMI="HAL 9000"

在前三个终端中,可观察新ReplicaSet的创建,以及新/旧Pod按容器编排进行的创建/删除的过程。还将看到与状态更改所对应的数据行的变化(Pod的期望数、当前数、更新日期、可用/准备)。在进行另一个变更之前,先扩展一下Deployment的ReplicaSet数量:
kubectl scale --replicas 3 deployment myapp

kubectl set命令仅限于设置环境变量、图像、资源请求/限制、标签选择器、ServiceAccounts和RoleBindings(基于角色的访问控制,或RBAC)。

kubectl patch命令则更为通用。它接受JSON或YAML来替换或合并特定的字段。举个简单的例子,我们可以绕过调度程序,强制让所有Pod运行在一个节点上,命令如下:

首先,看看我们的应用程序的副本目前运行在不同的节点:
kubectl get pods -l run=myapp -o wide # 查看NODE列

选定一个节点名,并以patch的方式调整Deployment:
NODE_NAME=$(kubectl get pods -l run=myapp -o 
jsonpath={.items[0].spec.nodeName})
kubectl patch deployment myapp -p
'{"spec":{"template":{"spec:{"nodeName":"'$NODE_NAME'"
}'}}}
确认所有Pod被分配到同一个节点上:
kubectl get pods -l run=myapp -o wide

实际上,还有更好的方式调整调度器,例如标签中心节点,使用node selector,affinities和anti-affinities,taints和tolerations等等。

声明式的配置方法

前面所演练的使Kubernetes知道如何运行,我们只是告诉Kubernetes我们要什么。执行的是只读的get,describe,logs命令,然后获取预期的结果,还有调试命令如port-forward,exec,cp和delect(用于替换而非修复Pod),这些命令帮助您理解了Kubernetes的重要概念。在实际应用上,这些命令使用并不多。

Kubernetes的强大主要是它的声明式API和控制器,接下来学习如何告诉Kubernetes要干什么。

您只需要使用kubectl apply和YAML(或JSON)就来展现Kubernetes保存在etcd中的状态,我们称之为manifest。

您可以简单地通过如下命令,获取在运行对象的manifest:
kubectl get -o yaml --export:
kubectl get deployment myapp -o yaml --export > myapp-deployment.yaml
kubectl get service myapp -o yaml --export > myapp-service.yaml

# ReplicaSet和pod是被控制对象,所以不需要进行manifest。

一个manifest实际上,并不需要保存所有状态。其中一些是由Kubernetes补充的。export选项自动删除了部分status和元数据(UID、创建时间戳等),但您可能按需要删除更多的配置数据,例如默认值等。

以下提供了Deployment和Service的标准示例,内容为:
# myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
  run: myapp
template:
metadata:
  labels:
    run: myapp
spec:
  containers:
  - name: myapp
    image: psdocker/myapp:mytag
    env:
    - name: WHOAMI
      value: "HAL 9000"
# myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
run: myapp
ports:
- port: 80
targetPort: 3000

为了证明它们的工作原理,我们先删除全部对象,然后再应用manifest来重新生成对象:
kubectl delete deployment myapp
kubectl delete service myapp
kubectl apply -f myapp-deployment.yaml -f myapp-service.yaml

如果我们没有删除Deployment和Service,那它们只是会进行更新来匹配manifest。

kubectl apply命令具有幂等的特征。即我们可以多次编辑manifest,然后再次去应用它。例如,修改了副本数配置后再应用。

如需进一步了解Kubectl apply,可以参考Kubernetes的API参考。

对于具有多个环境的复杂应用,直接对manifest进行管理会非常困难。更好的是通过工具帮助我们管理配置。目前已有不错的工具有kustomize(从1.14版开始就成为kubectl的一部分)、Helm和Jsonne等。对于整体构建和部署过程,并需要在manifest注入带标记的镜像,skaffold是一个值得推荐的工具。

总结

在本教程中,我们讨论了Kubernetes上无状态应用程序的基本组建:Deployment、ReplicaSet、Pods和Service。以及围绕它们进行的状态获取,执行等功能的命令和声明方法。我们只是以最基本的方式使用了 Deployment。在生产环境中,您应该需要设置CPU和内存请求和限制,并且您可能还对自动缩放和其他功能会感兴趣。

有几本好书可以用于学习Kubernetes。我读过且毫不犹豫推荐的唯一一本书是《Kubernetes in Action》(Manning著)。

当然,官方文档是一个很好的参考。

最后就是正确地使用大多数谷歌搜索的顶部结果。

原文链接:Imperative vs. Declarative — a Kubernetes Tutorial(翻译:易理林)

0 个评论

要回复文章请先登录注册