0%

简介

fsck(File System Consistency Check)是Linux的实用工具,用于检查文件系统是否存在错误或未解决的问题。该工具可以修复潜在的错误并生成报告。

默认情况下,Linux发行版附带此工具。使用fsck不需要特定的步骤或安装过程。打开终端后,就可以利用该工具的功能了。

按照本指南学习如何使用fsck在Linux上检查和修复文件系统。本教程将列出有关如何使用该工具以及用例的示例。

先决条件

  • Linux或类UNIX系统
  • 访问终端或命令行
  • 具有root权限的用户可以运行该工具

何时在Linux中使用fsck

fsck工具可以在多种情况下使用:

  • 使用fsck作为预防性维护或在系统出现问题时运行文件系统检查。
  • fsck可以诊断的一个常见问题是系统何时无法启动
  • 另一个是当系统上的文件损坏时出现输入/输出错误
  • 还可以使用fsck实用工具检查外部驱动器(例如SD卡USB闪存驱动器)的运行状况

基本的fsck语法

fsck实用工具的基本语法遵循以下模式:

1
fsck <options> <filesystem>

在上面的示例中,filesystem 可以是设备,分区,挂载点等。还可以在命令末尾使用特定于文件系统的选项。

如何检查和修复文件系统

在检查和修复文件系统之前,需要执行几个步骤。

查看已安装的磁盘和分区

要查看系统上所有已安装的设备并检查磁盘位置,请使用Linux中可用的工具之一。例如,使用df 命令列出文件系统磁盘:

1
df -h

df-tool

该工具可以打印系统上文件系统的使用情况。记下要使用fsck命令检查的磁盘。

例如,要查看第一个磁盘的分区,请使用以下命令:

1
sudo parted /dev/sda 'print'

sda是Linux指代第一个SCSI磁盘的方式。如果有两个,则第二个为sdb,依此类推。

在我们的示例中,由于该虚拟机上只有一个分区,因此得到了一个结果。如果有更多的分区,我们将获得更多的结果。

列出Linux分区时的终端输出

此处的磁盘名称为**/dev/sda** ,然后在“Number”列中显示分区的编号。在我们的例子中是:sda1。

卸载磁盘

必须先卸载磁盘或分区,然后才能使用fsck进行磁盘检查。如果尝试在已安装的磁盘或分区上运行fsck,则会收到警告:
尝试卸载已安装的磁盘或分区时的警告

确保运行unmount命令:

1
sudo umount /dev/sdb

替换*/dev/sdb*为要卸载的设备。


注意:我们不能卸载根文件系统。因此,现在fsck不能在正在运行的计算机上使用。


运行fsck检查错误

现在已经卸载了磁盘,就可以运行了fsck。要检查第二个磁盘,请输入:

1
sudo fsck /dev/sdb

运行fsck命令以检查第二个磁盘后的输出

上面的示例显示了正常磁盘的输出。如果磁盘上有多个问题,则每个错误都会出现一个提示,需要手动确认操作。

fsck实用工具返回的退出代码如下:

fsck命令可能的退出代码。

挂载磁盘

完成检查和修复设备后,请挂载磁盘,以便可以再次使用它。

在本例中,我们将重新安装sdb磁盘:

1
mount /dev/sdb

使用fsck进行试运行

在执行实时检查之前,可以使用fsck进行测试运行。将**-N** 选项传递给fsck命令以执行测试:

1
sudo fsck -N /dev/sdb

输出显示将发生的情况,但不执行任何操作。

使用fsck自动修复检测到的错误

要尝试解决潜在问题而没有任何提示,请将**-y选项传递给fsck**。

1
sudo fsck -y /dev/sdb

跳过修复,但在输出中显示fsck错误

如果要检查文件系统上的潜在错误而不进行修复,请使用**-n**选项。

1
sudo fsck -n /dev/sdb

使用-n选项可打印错误而不进行修复

强制fsck执行文件系统检查

在正常的设备上执行fsck时,该工具会跳过文件系统检查。如果要强制检查文件系统,请使用该**-f** 选项。

1
sudo fsck -f /dev/sdb

强制fsck工具执行文件系统检查

即使认为没有问题,也会执行扫描以搜索损坏。

一次在所有文件系统上运行fsck

如果要一次性检查所有使用fsck的文件系统,请传递该**-A标志。此选项将遍历/etc/fstab 中所有的磁盘并执行检查。

由于无法在正在运行的计算机上卸载根文件系统,因此请添加**-R** 选项以跳过它们:

1
fsck -AR

在特定文件系统上跳过fsck

如果要fsck跳过检查文件系统,则需要在文件系统之前添加**-t** 。

例如,要跳过ext3文件系统,请运行以下命令:

1
sudo fsck -AR -t noext3 -y

我们添加**-y**了跳过提示。

在已挂载的文件系统上跳过fsck

为确保不在已挂载的文件系统上运行fsck,请添加该**-M** 选项。该标志告诉fsck工具跳过任何已挂载的文件系统。

为了说明挂载前后的区别,我们将在sdb挂载时和卸载后分别执行fsck检查。

1
sudo fsck -M /dev/sdb

fsck工具的输出可跳过任何已挂载的文件系统

sdb被挂载时,该工具退出而不运行检查。然后,我们卸载sdb并再次运行相同的命令。这次,fsck检查磁盘并将其报告为正常磁盘或有错误。


注意:如果想要删除第一行标题“fsck from util-linux 2.31.1”,请使用**-T**选项。


在Linux根分区上运行fsck

正如我们已经提到的,fsck无法检查正在运行的计算机上的根分区,因为它们已经挂载并正在使用中。但是,如果进入恢复模式并运行fsck检查,是可以检查Linux根分区的。

1.为此,请通过GUI或使用终端打开或重新启动计算机:

1
sudo reboot

2.在启动过程中按住Shift键。出现GNU GRUB菜单。

3.选择Ubuntu的高级选项

Linux恢复模式

4.然后,选择末尾带有(恢复模式)的条目。让系统加载到“恢复菜单”中。

5.从菜单中选择fsck

Linux恢复菜单中选择fsck工具

6.通过在提示符下选择**<是>**进行确认。

选择fsck时的恢复模式确认消息

7.完成后,在恢复菜单中选择“恢复”以启动计算机。

完成检查后

如果fsck被中断怎么办

正常来说,不应该打断正在进行的fsck检查。但是,如果该过程被中断,fsck将完成正在进行的检查,然后停止。

如果该实用工具在检查过程中发现错误,则如果中断,它将不会尝试修复任何问题。可以在下次重新运行检查。

fsck Linux命令选项列表

最后,下面是可与fsck Linux实用工具一起使用的选项列表。

选项 描述
-a 尝试自动修复文件系统错误。不会出现提示,因此请谨慎使用。
-A 检查/etc/fstab中列出的所有文件系统。
-C 显示检查ext2和ext3文件系统的进度。
-F 强制fsck检查文件系统。该工具甚至在文件系统看起来正常时也进行检查。
-l 锁定设备,以防止其他程序在扫描和修复期间使用该分区。
-M 不要检查已挂载的文件系统。挂载文件系统时,该工具返回退出代码0。
-N 做空试。输出显示fsck在不执行任何操作的情况下将执行的操作。警告或错误消息也将被打印。
-P 用于在多个文件系统上并行运行扫描。请谨慎使用。
-R 使用-A选项时,告诉fsck工具不要检查根文件系统。
-r 打印设备统计信息。
-t 指定要使用fsck检查的文件系统类型。请查阅手册页以获取详细信息。
-T 工具启动时隐藏标题。
-y 尝试在检查期间自动修复文件系统错误。
-V 详细输出。

结论

现在我们知道了如何使用fsck Linux命令来检查和修复文件系统。该指南提供了该工具的功能和示例。

在运行列出的命令之前,请确保具有root权限。有关所有选项的详细说明,还可以查阅该工具的手册文件或访问fsck Linux手册页

Helm简介

Helm是一个可简化Kubernetes应用程序安装和管理的工具。Helm可以理解为Kubernetesapt/yum/homebrew

此文档使用的是Helmv3版本。如果我们使用的是Helm v2,请转到helm-v2分支。请参阅“Helm状态”以获取有关不同Helm版本的更多详细信息。

Helm状态

Helm v3于2019年11月发布。新老版本的接口非常相似,但是Helm的体系结构和内部架构发生了重大变化。有关更多详细信息,请查看Helm 3中的内容。

Helm v2计划支持1年“维护模式”。它指出以下内容:

  • 6个月的bug修复,直到2020年5月13日
  • 6个月的安全修复,直到2020年11月13日
  • 2020年11月13日开始,对Helm v2的支持将终止

为什么使用Helm

Helm通常被称为Kubernetes应用程序包管理器。那么,使用Helm而不直接使用kubectl有什么好处呢?

目标

这些实验提供了关于使用Helm优于直接通过Kubectl使用Kubernetes的优势的见解。后续的几个实验都分为两种情况:第一种情况提供了如何使用kubectl执行任务的示例;第二种情况提供了使用Helm的示例。完成所有实验后,我们可以:

  • 了解Helm的核心概念
  • 了解使用Helm而非直接使用Kubernetes进行部署的优势:
    • 应用管理
    • 更新
    • 配置
    • 修订管理
    • 储存库和Chart图表共享

前提

  • 有一个正在运行的Kubernetes集群。有关创建集群的详细信息,请参阅《 IBM Cloud Kubernetes服务Kubernetes入门指南》。
  • 已通过Kubernetes集群安装并初始化了Helm。有关Helm入门,请参阅在IBM Cloud Kubernetes Service上安装Helm或《 Helm快速入门指南》。

Helm概览

Helm是可简化Kubernetes应用程序安装和管理的工具。它使用一种称为“Chart”的打包格式,该格式是描述Kubernetes资源的文件的集合。它可以在任何地方(笔记本电脑,CI/CD等)运行,并且可用于各种操作系统,例如OSX,Linux和Windows

helm-architecture

Helm 3Helm 2客户端-服务器架构转向了客户端架构。客户端仍称为helm,并且有一个改进的Go库,该库封装了Helm逻辑,以便可以由不同的客户端使用。客户端是一个CLI,用户可以与它进行交互以执行不同的操作,例如安装/升级/删除等。客户端与Kubernetes API服务器和Chart存储库进行交互。它将Helm模板文件渲染为Kubernetes清单文件,用于通过Kubernetes APIKubernetes集群上执行操作。有关更多详细信息,请参见Helm架构

Chart被组织为目录内文件的集合,其中目录名是Chart的名称。它包含模板YAML文件,这些模板有助于在运行时提供配置值,并且无需修改YAML文件。这些模板基于Go模板语言,Sprig lib中的功能和其他专用功能提供了编程逻辑。

Chart存储库是可以存储和共享打包的Chart的位置。这类似于Docker中的镜像存储库。有关更多详细信息,请参考《Chart存储库指南》。

Helm概念

Helm术语:

  • Chart - 包含在Kubernetes集群中运行的应用程序,工具或服务所需的所有资源定义。Chart基本上是预先配置的Kubernetes资源的软件包。
  • Config - 包含可合并到Chart中以创建可发布对象的配置信息。
  • helm - helm客户端。它将Chart呈现为清单文件。它直接与Kubernetes API服务器交互以安装,升级,查询和删除Kubernetes资源。
  • Release - 在Kubernetes集群中运行的Chart实例。
  • Repository - 存储Chart的仓库,可以与他人共享。

Lab0 安装Helm

可以从源代码或预构建的二进制发行版中安装Helm客户端(helm)。在本实验中,我们将使用Helm社区的预构建二进制发行版(Linux amd64)。有关更多详细信息,请参阅Helm安装文档

前提依赖

  • Kubernetes集群

安装Helm客户端

  1. 下载适用于环境的最新版本的Helm v3,以下步骤适用于Linux amd64,请根据环境调整示例。
  2. 解压:$ tar -zxvf helm-v3.<x>.<y>-linux-amd64.tgz
  3. 在解压后的目录中找到helm二进制文件,并将其移至所需位置:mv linux-amd64/helm /usr/local/bin/helm。最好是将复制到的位置设置到path环境变量,因为它避免了必须对helm命令进行路径设置。
  4. 现在已安装了Helm客户端,可以使用helm help命令对其进行测试。

结论

现在可以开始使用Helm了。

Lab1 使用Helm部署应用

让我们研究一下Helm如何使用Chart来简化部署。我们首先使用kubectl将应用程序部署到Kubernetes集群,然后展示如何通过使用Helm部署同一应用程序。

该应用程序是Guestbook App,它是一个多层级的Web应用程序。

场景1: 使用kubectl部署应用

在本部分的实验中,我们将使用Kubernetes客户端kubectl部署应用程序。使用该应用程序的版本1进行部署。

如果已经从kube101安装了guestbook应用程序,请跳过本节,转到场景2中的helm示例。

克隆Guestbook App存储库以获取文件:

1
git clone https://github.com/IBM/guestbook.git
  1. 使用克隆的Git库中的配置文件来部署容器,并使用以下命令为它们创建服务:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $ cd guestbook/v1

    $ kubectl create -f redis-master-deployment.yaml
    deployment.apps/redis-master created

    $ kubectl create -f redis-master-service.yaml
    service/redis-master created

    $ kubectl create -f redis-slave-deployment.yaml
    deployment.apps/redis-slave created

    $ kubectl create -f redis-slave-service.yaml
    service/redis-slave created

    $ kubectl create -f guestbook-deployment.yaml
    deployment.apps/guestbook-v1 created

    $ kubectl create -f guestbook-service.yaml
    service/guestbook created

    有关更多详细信息,请参阅README

  2. 查看guestbook

    现在,我们可以通过在浏览器中打开刚创建的留言簿来玩(可能需要一些时间才能显示出来)。

    • 本地主机:如果我们在本地运行Kubernetes,请在浏览器中导航至http://localhost:3000以查看留言簿。

    • 远程主机:

      • 要查看远程主机上的留言簿,请在$ kubectl get services输出的EXTERNAL-IPPORTS列中找到负载均衡器的外部IP和端口。

        1
        2
        3
        4
        5
        6
        $ kubectl get services
        NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
        guestbook LoadBalancer 172.21.252.107 50.23.5.136 3000:31367/TCP
        redis-master ClusterIP 172.21.97.222 <none> 6379/TCP
        redis-slave ClusterIP 172.21.43.70 <none> 6379/TCP
        .........

        在这种情况下,URL为http://50.23.5.136:31367

        注意:如果未分配外部IP,则可以使用以下命令获取外部IP

        1
        2
        3
        $ kubectl get nodes -o wide
        NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
        10.47.122.98 Ready <none> 1h v1.10.11+IKS 173.193.92.112 Ubuntu 16.04.5 LTS 4.4.0-141-generic docker://18.6.1
      • 在这种情况下,URL为http://173.193.92.112:31367。WW在浏览器中导航到给定的输出(例如http://50.23.5.136:31367)。应该看到浏览器显示如下:

        guestbook-page

场景2: 使用Helm部署应用

在实验的这一部分,我们将使用Helm部署应用程序。我们将设置guestbook-demo的发行版名称,以使其与之前的部署区分开。可在此处获得Helm chart。克隆Helm 101存储库以获取文件:

1
git clone https://github.com/IBM/helm101

Chart被定义为描述一组相关的Kubernetes资源的文件的集合。我们先查看文件,然后再安装。guestbookchart文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├──Chart.yaml \\包含有关信息的YAML文件
├──LICENSE \\许可证
├──README.md \\帮助文档,提供有关chart用法,配置,安装等信息
├──template \\模板目录,当与values.yaml结合使用时将生成有效的Kubernetes清单文件
│ ├──_helpers.tpl \\在整个chart中重复使用的模板帮助程序/定义
│ ├──guestbook-deployment.yaml \\ Guestbook应用程序容器资源
│ ├──guestbook-service.yaml \\ Guestbook应用服务资源
│ ├──NOTES.txt \\一个纯文本文件,包含有关如何在安装后访问应用程序的简短使用说明
│ ├──redis-master-deployment.yaml \\ Redis主容器资源
│ ├──redis-master-service.yaml \\ Redis主服务资源
│ ├──redis-slave-deployment.yaml \\ Redis从属容器资源
│ └──redis-slave-service.yaml \\ Redis从属服务资源
└──values.yaml \\chart的默认配置值

注意:上面显示的模板文件将被传递到Kubernetes清单文件中,然后再传递给Kubernetes API服务器。因此,它们映射到我们在使用kubectl时部署的清单文件(不包含READMENOTES)。

让我们继续并立即安装chart。如果helm-demo命名空间不存在,则需要使用以下命令创建它:

1
kubectl create namespace helm-demo
  1. 将应用程序作为Helm chart安装:
1
2
3
4
5
$ cd helm101/charts

$ helm install guestbook-demo ./guestbook/ --namespace helm-demo
NAME: guestbook-demo
...

我们应该看到类似于以下内容的输出:

1
2
3
4
5
6
7
8
9
10
11
12
NAME: guestbook-demo
LAST DEPLOYED: Mon Feb 24 18:08:02 2020
NAMESPACE: helm-demo
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get svc -w guestbook-demo --namespace helm-demo'
export SERVICE_IP=$(kubectl get svc --namespace helm-demo guestbook-demo -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:3000

chart的安装将执行Redis主服务器和从服务器以及guestbook应用的Kubernetes部署和服务创建。这是因为该chart是描述一组相关的Kubernetes资源的文件的集合,并且Helm通过Kubernetes API管理这些资源的创建。

查看部署状态:

1
2
3
$ kubectl get deployment guestbook-demo --namespace helm-dem
NAME READY UP-TO-DATE AVAILABLE AGE
guestbook-demo 2/2 2 2 51m

查看pod状态:

1
2
3
4
5
6
7
$ kubectl get pods --namespace helm-demo
NAME READY STATUS RESTARTS AGE
guestbook-demo-6c9cf8b9-jwbs9 1/1 Running 0 52m
guestbook-demo-6c9cf8b9-qk4fb 1/1 Running 0 52m
redis-master-5d8b66464f-j72jf 1/1 Running 0 52m
redis-slave-586b4c847c-2xt99 1/1 Running 0 52m
redis-slave-586b4c847c-q7rq5 1/1 Running 0 52m

查看service状态:

1
2
3
4
5
$ kubectl get services --namespace helm-demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
guestbook-demo LoadBalancer 172.21.43.244 <pending> 3000:31367/TCP 52m
redis-master ClusterIP 172.21.12.43 <none> 6379/TCP 52m
redis-slave ClusterIP 172.21.176.148 <none> 6379/TCP 52m
  1. 查看留言簿:

    现在,我们可以通过在浏览器中打开刚创建的留言簿来玩(可能需要一些时间才能显示出来)。

    • 本地主机:如果我们在本地运行Kubernetes,请在浏览器中导航至http://localhost:3000以查看留言簿。

    • 远程主机:

      • 要查看远程主机上的留言簿,请在$ kubectl get services输出的EXTERNAL-IPPORTS列中找到负载均衡器的外部IP和端口。

        1
        2
        3
        $ export SERVICE_IP=$(kubectl get svc --namespace helm-demo guestbook-demo -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
        $ echo http://$SERVICE_IP
        http://50.23.5.136

在这种情况下,URL为http://50.23.5.136:31367

注意:如果未分配外部IP,则可以使用以下命令获取外部IP

1
2
3
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
10.47.122.98 Ready <none> 1h v1.10.11+IKS 173.193.92.112 Ubuntu 16.04.5 LTS 4.4.0-141-generic docker://18.6.1
 - 在这种情况下,URL为`http://173.193.92.112:31367`。在浏览器中导航到给定的输出(例如`http://50.23.5.136:31367`)。应该看到浏览器显示如下:

   ![guestbook-page](https://gitee.com/lyyao09/cdn/raw/master/k8s/Helm101/guestbook-page.png)

结论

恭喜,我们现在已经通过两种不同的方法将应用程序部署到Kubernetes。从本实验中,我们可以看到,与使用kubectl相比,使用Helm所需的命令更少,思考的时间也更少(通过提供chart路径而不是单个文件)。 Helm的应用程序管理为用户提供了这种简单性。

Lab2 使用Helm更新应用

Lab1中,我们使用Helm安装了guestbook示例应用程序,并看到了相较于kubectl的优势。我们可能认为自己已经足够了解使用Helm。但是chart的更新或修改呢?我们如何更新和修改正在运行的应用?

在本实验中,我们将研究chart更改后如何更新正在运行的应用程序。为了说明这一点,我们将通过以下方式对原始留言簿的chart进行更改:

  • 删除Redis从节点并改为仅使用内存数据库
  • 将类型从LoadBalancer更改为NodePort

虽然是修改,但是本实验的目的是展示如何使用KubernetesHelm更新应用。那么,这样做有多容易呢?让我们继续看看。

场景1: 使用kubectl更新应用

在本部分的实验中,我们将直接使用Kubernetes更新以前部署的应用程序Guestbook

  1. 这是一个可选步骤,从技术上讲,更新正在运行的应用程序不是必需的。进行此步骤的原因是“整理”-我们要为已部署的当前配置获取正确的文件。这样可以避免在以后进行更新甚至回滚时犯错误。在此更新的配置中,我们删除了Redis从节点。要使目录与配置匹配,请移动/存档或仅从来文件夹中删除Redis从属文件:
1
2
3
cd guestbook/v1
rm redis-slave-service.yaml
rm redis-slave-deployment.yaml

注意:如果需要,可以稍后使用git checkout-命令来还原这些文件。

  1. 删除Redis从节点的ServicePod
1
2
3
4
$ kubectl delete svc redis-slave --namespace default
service "redis-slave" deleted
$ kubectl delete deployment redis-slave --namespace default
deployment.extensions "redis-slave" deleted
  1. Guestbook服务的yamlLoadBalancer更新为NodePort类型:
1
sed -i.bak 's/LoadBalancer/NodePort/g' guestbook-service.yaml
  1. 删除Guestbook运行时服务
1
kubectl delete svc guestbook --namespace default
  1. 重新创建具有NodePort类型的服务:
1
kubectl create -f guestbook-service.yaml
  1. 使用以下命令检查更新:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ kubectl get all --namespace default
NAME READY STATUS RESTARTS AGE
pod/guestbook-v1-7fc76dc46-9r4s7 1/1 Running 0 1h
pod/guestbook-v1-7fc76dc46-hspnk 1/1 Running 0 1h
pod/guestbook-v1-7fc76dc46-sxzkt 1/1 Running 0 1h
pod/redis-master-5d8b66464f-pvbl9 1/1 Running 0 1h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/guestbook NodePort 172.21.45.29 <none> 3000:31989/TCP 31s
service/kubernetes ClusterIP 172.21.0.1 <none> 443/TCP 9d
service/redis-master ClusterIP 172.21.232.61 <none> 6379/TCP 1h

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/guestbook-demo 3/3 3 3 1h
deployment.apps/redis-master 1/1 1 1 1h

NAME DESIRED CURRENT READY AGE
replicaset.apps/guestbook-v1-7fc76dc46 3 3 3 1h
replicaset.apps/redis-master-5d8b66464f 1 1 1 1h

注意:服务类型已更改(更改为NodePor),并且已为留言簿服务分配了新端口(在此输出情况下为31989)。所有redis-slave资源均已删除。

  1. 获取节点的公共IP,并重新访问应用提供的服务:
1
kubectl get nodes -o wide

场景2: 使用Helm更新应用

在本节中,我们将使用Helm更新以前部署的guestbook-demo应用程序。

在开始之前,让我们花几分钟看一下Helm与直接使用Kubernetes相比如何简化流程。 Helm使用模板语言为chart提供了极大的灵活性和强大的功能,从而为chart用户消除了复杂性。在留言簿示例中,我们将使用以下模板功能:

  • Values:提供访问传递到chart中的值的对象。例如在guestbook-service中,它包含以下类型:.Values.service.type。此行提供了在升级或安装期间设置服务类型的功能。
  • 控制结构:在模板中也称为“动作”,控制结构使模板能够控制生成的流程。一个例子是在redis-slave-service中,它包含行-if .Values.redis.slaveEnabled-。该行允许我们在升级或安装期间启用/禁用REDIS主/从。

如下所示,完整的redis-slave-service.yaml演示了在禁用slaveEnabled标志时文件如何变得冗余以及如何设置端口值。其他chart文件中还有更多的模板功能示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{{- if .Values.redis.slaveEnabled -}}
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
app: redis
role: slave
spec:
ports:
- port: {{ .Values.redis.port }}
targetPort: redis-server
selector:
app: redis
role: slave
{{- end }}

1.

1
helm list -n helm-demo

请注意,我们指定了名称空间。如果未指定,它将使用当前的名称空间上下文。我们应该看到类似于以下内容的输出:

1
2
3
$ helm list -n helm-demo
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
guestbook-demo helm-demo 1 2020-02-24 18:08:02.017401264 +0000 UTC deployed guestbook-0.2.0

list命令提供已部署chart(发行版)的列表,其中提供了chart版本,名称空间,更新(修订)数量等信息。

  1. 我们更新应用程序:
1
2
3
4
5
$ cd helm101/charts

$ helm upgrade guestbook-demo ./guestbook --set redis.slaveEnabled=false,service.type=NodePort --namespace helm-demo
Release "guestbook-demo" has been upgraded. Happy Helming!
...

Helm升级将采用现有版本,并根据提供的信息对其进行升级。我们应该看到类似于以下内容的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ helm upgrade guestbook-demo ./guestbook --set redis.slaveEnabled=false,service.type=NodePort --namespace helm-demo
Release "guestbook-demo" has been upgraded. Happy Helming!
NAME: guestbook-demo
LAST DEPLOYED: Tue Feb 25 14:23:27 2020
NAMESPACE: helm-demo
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
export NODE_PORT=$(kubectl get --namespace helm-demo -o jsonpath="{.spec.ports[0].nodePort}" services guestbook-demo)
export NODE_IP=$(kubectl get nodes --namespace helm-demo -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT

upgrade命令将应用程序升级到chart的指定版本,删除redis-slave资源,并将应用程序service.type更新为NodePort

使用kubectl get all --namespace helm-demo获取更新内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ kubectl get all --namespace helm-demo
NAME READY STATUS RESTARTS AGE
pod/guestbook-demo-6c9cf8b9-dhqk9 1/1 Running 0 20h
pod/guestbook-demo-6c9cf8b9-zddn2 1/1 Running 0 20h
pod/redis-master-5d8b66464f-g7sh6 1/1 Running 0 20h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/guestbook-demo NodePort 172.21.43.244 <none> 3000:31202/TCP 20h
service/redis-master ClusterIP 172.21.12.43 <none> 6379/TCP 20h

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/guestbook-demo 2/2 2 2 20h
deployment.apps/redis-master 1/1 1 1 20h

NAME DESIRED CURRENT READY AGE
replicaset.apps/guestbook-demo-6c9cf8b9 2 2 2 20h
replicaset.apps/redis-master-5d8b66464f 1 1 1 20h

注意:服务类型已更改(更改为NodePort),并且已为留言簿服务分配了新端口(在此输出情况下为31202)。所有redis-slave资源均已删除。

当我们使用helm list -n helm-demo命令检查Helm版本时,可以看到revision和日期已更新:

1
2
3
$ helm list -n helm-demo
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
guestbook-demo helm-demo 2 2020-02-25 14:23:27.06732381 +0000 UTC deployed guestbook-0.2.0

获取节点的公共IP,并重新访问应用提供的服务:

1
kubectl get nodes -o wide

结论

恭喜,现在已经更新了应用程序! Helm不需要任何手动更改资源,因此非常容易升级!所有配置都可以在命令行上即时设置,也可以使用替代文件设置。从将逻辑添加到模板文件后就可以实现这一点,这取决于flag标识,启用或禁用此功能。

Lab 3. 跟踪已部署的应用程序

假设我们部署了应用程序的不同发行版(即升级了正在运行的应用程序)。如何跟踪版本以及如何回滚?

场景1: 使用Kubernetes进行修订管理

在本部分的实验中,我们应该直接使用Kubernetes来说明留言簿的修订管理,但是我们不能。这是因为Kubernetes不为修订管理提供任何支持。我们有责任管理系统以及所做的任何更新或更改。但是,我们可以使用Helm进行修订管理。

场景2: 使用Helm进行修订管理

在本部分的实验中,我们将使用Helm来说明对已部署的应用程序guestbook-demo的修订管理。

使用Helm,每次进行安装,升级或回滚时,修订版本号都会增加1。第一个修订版本号始终为1。Helm将发布元数据保留在Kubernetes集群中存储的Secrets(默认)或ConfigMap中。每当发行版更改时,都会将其附加到现有数据中。这为Helm提供了回滚到先前版本的功能。

让我们看看这在实践中如何工作。

  1. 检查部署的数量:

应该看到类似于以下的输出,因为在Lab 1中进行初始安装后,我们在Lab 2中进行了升级。

1
2
3
4
$ helm history guestbook-demo -n helm-demo
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Feb 24 18:08:02 2020 superseded guestbook-0.2.0 Install complete
2 Tue Feb 25 14:23:27 2020 deployed guestbook-0.2.0 Upgrade complete
  1. 回滚到以前的版本:

在此回滚中,Helm将检查从修订版1升级到修订版2时发生的更改。此信息使它能够调用Kubernetes API服务,以根据初始部署更新已部署的应用程序-换句话说,使用Redis slave并使用负载平衡器。

1
2
$ helm rollback guestbook-demo 1 -n helm-demo
Rollback was a success! Happy Helming!
  1. 再次检查历史记录:

应该看到类似于以下的输出:

1
2
3
4
5
$ helm history guestbook-demo -n helm-demo
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Mon Feb 24 18:08:02 2020 superseded guestbook-0.2.0 Install complete
2 Tue Feb 25 14:23:27 2020 superseded guestbook-0.2.0 Upgrade complete
3 Tue Feb 25 14:53:45 2020 deployed guestbook-0.2.0 Rollback to 1
  1. 检查回滚结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ kubectl get all --namespace helm-demo
NAME READY STATUS RESTARTS AGE
pod/guestbook-demo-6c9cf8b9-dhqk9 1/1 Running 0 20h
pod/guestbook-demo-6c9cf8b9-zddn 1/1 Running 0 20h
pod/redis-master-5d8b66464f-g7sh6 1/1 Running 0 20h
pod/redis-slave-586b4c847c-tkfj5 1/1 Running 0 5m15s
pod/redis-slave-586b4c847c-xxrdn 1/1 Running 0 5m15s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/guestbook-demo LoadBalancer 172.21.43.244 <pending> 3000:31367/TCP 20h
service/redis-master ClusterIP 172.21.12.43 <none> 6379/TCP 20h
service/redis-slave ClusterIP 172.21.232.16 <none> 6379/TCP 5m15s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/guestbook-demo 2/2 2 2 20h
deployment.apps/redis-master 1/1 1 1 20h
deployment.apps/redis-slave 2/2 2 2 5m15s

NAME DESIRED CURRENT READY AGE
replicaset.apps/guestbook-demo-26c9cf8b9 2 2 2 20h
replicaset.apps/redis-master-5d8b66464f 1 1 1 20h
replicaset.apps/redis-slave-586b4c847c 2 2 2 5m15s

从输出中可以再次看到,应用程序服务是LoadBalancer的服务类型,并且Redis主/从部署已返回。这显示了实验2中升级的完整回滚。

结论

从这个实验中,我们可以说Helm很好地进行了修订管理,而Kubernetes没有内置的功能!我们可能想知道为什么需要helm rollback,因为重新执行helm upgrade也可以回到老版本。这是一个很好的问题。从技术上讲,我们应该最终部署相同的资源(具有相同的参数)。但是,使用helm rollback的好处是,Helm可以管理(即记住)以前的helm install\upgrade的所有变体/参数。通过helm upgrade进行回滚需要我们手动跟踪先前执行命令的方式。这不仅繁琐,而且容易出错。让Helm管理所有这些工作更加容易,安全和可靠,并且我们需要做的所有事情都告诉它可以使用哪个以前的版本,其余的都可以完成。

Lab 4. 共享Helm Charts

提供应用程序的一个关键方面意味着与他人共享。共享可以是直接的(由用户或在CI/CD管道中),也可以作为其他chart的依赖项。如果人们找不到你的应用程序,那么他们就无法使用它。

共享的一种方法是使用chart库,该仓库可以存储和共享打包的chart。由于chart库仅适用于Helm,因此我们将仅查看Helm chart的用法和存储。

从公共仓库中获取Chart

Helm charts可以在远程存储库或本地环境/存储库中使用。远程存储库可以是公共的,例如Bitnami ChartsIBM Helm Charts,也可以是托管存储库,例如在Google Cloud StorageGitHub上。有关更多详细信息,请参阅《 Helm Chart存储库指南》。我们可以通过在本实验中检查chart索引文件来了解有关chart存储库结构的更多信息。

在本部分的实验中,我们将展示如何从Helm101存储库中安装留言簿chart

  1. 检查系统上配置的存储库:
1
2
$ helm repo list
Error: no repositories to show

注意:默认情况下,Helm v3未安装chart存储库,而是期望我们自己为要使用的chart添加存储库。 Helm Hub可以集中搜索公共可用的分布式chart。使用Helm Hub,我们可以找到所需chart,然后将其添加到本地存储库列表中。 Helm chart存储库(如Helm v2)处于“维护模式”,将于2020年11月13日弃用。有关更多详细信息,请参见项目状态

  1. 添加helm101仓库:
1
2
$ helm repo add helm101 https://ibm.github.io/helm101/
"helm101" has been added to your repositories

​ 还可以通过运行以下命令在存储库中搜索chart

1
2
3
$ helm search repo helm101
NAME CHART VERSION APP VERSION DESCRIPTION
helm101/guestbook 0.2.1 A Helm chart to deploy Guestbook three tier web...
  1. 安装chart

如前所述,我们将安装Helm101存储库中的留言簿chart。当将仓库添加到我们的本地仓库清单中时,我们可以使用repo name/chart name(即helm101/guestbook)来引用chart。要查看实际效果,将应用程序安装到名为repo-demo的新命名空间中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$kubectl create namespace repo-demo
$helm install guestbook-demo helm101/guestbook --namespace repo-demo

$helm install guestbook-demo helm101/guestbook --namespace repo-demo
NAME: guestbook-demo
LAST DEPLOYED: Tue Feb 25 15:40:17 2020
NAMESPACE: repo-demo
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get svc -w guestbook-demo --namespace repo-demo'
export SERVICE_IP=$(kubectl get svc --namespace repo-demo guestbook-demo -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:3000

检查是否按预期部署了该版本,如下所示:

1
2
3
$ helm list -n repo-demo
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
guestbook-demo repo-demo 1 2020-02-25 15:40:17.627745329 +0000 UTC deployed guestbook-0.2.1

结论

本实验简要介绍了Helm存储库,以显示如何安装chart。共享chart的能力意味着更易于使用。

什么是Debug

Debug调试是为了找到并修复代码中的错误。这是朝着编写没有bug的代码的方向迈出的重要一步,而没有bug的代码可以创建可靠的软件。

因此,我将以简单的步骤说明如何在IntelliJ IDEA中调试Maven项目的Test测试。

Debug测试

Step 1 :

Debug测试例需要使用到Maven surefire plugin插件。以下使用到的命令是在Ubuntu上执行的。

首先是在需要调试的代码行中打断点。为此,只需在代码编辑区域中单击行的左上角,即可在调试期间暂停测试。单击时将出现一个红点

Step 2 :

进入包含maven项目的集成测试的目录后,在命令行上键入以下命令。

1
2
cd <path-to-the-directory-containing-your-maven-project's-integrationtests>
mvn clean install -Dmaven.surefire.debug

测试将自动暂停,并在端口5005上等待远程调试器。(端口5005为默认端口)。我们可以在命令行中看到一条语句,通知它正在监听端口5005。

1
Listening for transport dt_socket at address: 5005

如果需要配置其他端口,则可以将更详细的值传递给上述命令。

1
mvn clean install -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -Djava.compiler=NONE"

此命令将会监听端口8000而不是5005。

Step 3 :

如果是第一次运行调试器,则必须在IntelliJ IDEA中编辑Debug配置。如果已经完成了配置并将远程调试器端口设置为5005,则无需再次编辑配置。

Debug配置可以安装如下流程进行编辑:

  • 在IDE中转到“Run –> Edit Configurations…”
  • 在出现的对话框中,单击左上角的“ +”号
  • 在下拉列表中找到“Remote”选项
  • 在出现的下一个窗口中,在必须指定端口的地方指定端口
  • 然后“Apply ”,然后单击“Ok”。

Step 4 :

然后,可以使用IDE附加到正在运行的测试。

  • 转到Run –> Debug…
  • 然后选择之前指定的配置

现在,测试已附加到远程调试器。上面就是我们需要做的所有事情。

测试将在我们之前指定的断点处暂停。在运行测试时,进出请求的详细信息可以在IDE中看到。我们也可以单击并逐个删除断点,并在每次暂停后通过IDE恢复程序。

以下是Kubernetes patterns手册中为初学者总结的必须知道的十大设计模式。熟悉这些模式将有助于理解Kubernetes的基本概念,这反过来又将有助于讨论和设计基于Kubernetes的应用程序。

Kubernetes中有许多重要的概念,但下面这些是最重要的概念:

top_10_kubernetes_patterns

为了帮助理解,这些模式被组织成以下几个类别,灵感来自Gang of Four’s的设计模式。

基本模式

这些模式代表了容器化应用程序必须遵守的原则和最佳实践,以便成为优秀的云公民。不管应用程序的性质如何,我们都应该遵循这些准则。遵循这些原则将有助于确保我们的应用程序适用于Kubernetes上的自动化。

健康探测模式

Health Probe要求每个容器都应该实现特定的API,以帮助平台以最健康的方式观察和管理应用程序。为了完全自动化,云本地应用程序必须具有高度的可观察性,允许推断其状态,以便Kubernetes可以检测应用程序是否已启动并准备好为请求提供服务。这些观察结果会影响Pods的生命周期管理以及将流量路由到应用程序的方式。

可预测需求模式

可预测的需求解释了为什么每个容器都应该声明它的资源配置文件,并且只限于指定的资源需求。在共享云环境中成功部署应用程序、管理和共存的基础依赖于识别和声明应用程序的资源需求和运行时依赖性。此模式描述应该如何声明应用程序需求,无论它们是硬运行时依赖项还是资源需求。声明的需求对于Kubernetes在集群中的应用程序找到合适的位置至关重要。

自动放置模式

自动放置解释了如何影响多节点集群中的工作负载分布。放置是Kubernetes调度器的核心功能,用于为满足容器资源请求的节点分配新的pod,并遵守调度策略。该模式描述了Kubernetes调度算法的原理以及从外部影响布局决策的方式。

结构模式

拥有良好的云本地容器是第一步,但还不够。下一步是重用容器并将它们组合成Pod以实现预期的结果。这一类中的模式侧重于结构化和组织Pod中的容器,以满足不同的用例。

Init Container模式

Init容器为初始化相关的任务和主应用程序容器引入了一个单独的生命周期。Init容器通过为不同于主应用程序容器的初始化相关任务提供单独的生命周期来实现关注点的分离。这个模式引入了一个基本的Kubernetes概念,当需要初始化逻辑时,这个概念在许多其他模式中使用。

Sidecar模式

Sidecar描述了如何在不改变容器的情况下扩展和增强已有容器的功能。此模式是基本的容器模式之一,它允许单用途容器紧密地协作。

行为模式

这些模式描述了管理平台确保的pod的生命周期保证。根据工作负载的类型,Pod可以作为批处理作业一直运行到完成,也可以计划定期运行。它可以作为守护程序服务或单例运行。选择正确的生命周期管理原语将帮助我们运行具有所需保证的Pod。

批处理模式

批处理作业描述如何运行一个独立的原子工作单元直到完成。此模式适用于在分布式环境中管理独立的原子工作单元。

有状态服务模式

有状态服务描述如何使用Kubernetes创建和管理分布式有状态应用程序。这类应用程序需要持久身份、网络、存储和普通性等特性。StatefulSet原语为这些构建块提供了强有力的保证,非常适合有状态应用程序的管理。

服务发现模式

服务发现解释了客户端如何访问和发现提供应用程序服务的实例。为此,Kubernetes提供了多种机制,这取决于服务使用者和生产者位于集群上还是集群外。

高级模式

此类别中的模式更复杂,代表更高级别的应用程序管理模式。这里的一些模式(比如Controller)是永不过时的,Kubernetes本身就是建立在这些模式之上的。

Controller模式

控制器是一种模式,它主动监视和维护一组处于所需状态的Kubernetes资源。Kubernetes本身的核心由一组控制器组成,这些控制器定期监视并协调应用程序的当前状态与声明的目标状态。这个模式描述了如何利用这个核心概念为我们自己的应用程序扩展平台。

Operator模式

Operator是一个控制器,它使用CustomResourceDefinitions将特定应用程序的操作知识封装为算法和自动化形式。Operator模式允许我们扩展控制器模式以获得更大的灵活性和表现力。Kubernetes的Operator越来越多,这种模式正成为操作复杂分布式系统的主要形式。

总结

今天,Kubernetes是最流行的容器编排平台。它由所有主要的软件公司共同开发和支持,并作为一项服务由所有主要的云提供商提供。Kubernetes支持Linux和Windows系统,以及所有主要的编程语言。该平台还可以编排和自动化无状态和有状态的应用程序、批处理作业、周期性任务和无服务器工作负载。这里描述的模式是Kubernetes附带的一组更广泛的模式中最常用的模式,如下所示。

KubernetePatternsLevels

Kubernetes是新的应用程序可移植层。如果你是一个软件开发人员或架构师,Kubernetes很可能会以这样或那样的形式成为你生活的一部分。学习这里描述的Kubernetes模式将改变我们对这个平台的看法。我相信Kubernetes和由此产生的概念将成为面向对象编程概念的基础。

这里的模式试图创建类似Gang of Four的设计模式,但是用于容器编排。阅读这篇文章一定不是结束,而是你的Kubernetes之旅的开始。

Init模式

初始化逻辑通常在编程语言中很常见。在面向对象编程语言中,我们有构造函数的概念。构造函数是一个函数(或方法),每当对象被实例化时都会被调用。构造器的目的是“准备”对象以完成它应该做的工作。例如,它设置变量的默认值,创建数据库连接对象,确保对象正确运行所需的先决条件的存在。例如,如果创建了一个user对象,那么它至少需要用户的用户名、名和姓,这样它才能正常工作。不同语言之间的构造函数实现是不同的。但是,所有这些都只被调用一次,并且只在对象实例化时调用。

初始化模式的目的是将对象与其初始化逻辑解耦。因此,如果一个对象需要一些种子数据输入到数据库中,这就属于构造函数逻辑而不是应用程序逻辑。这允许我们更改对象的“启动”方式,而不影响其“工作”方式。

Kubernetes使用相同的模式。虽然对象是面向对象语言的原子单元,但是Kubernetes有Pods。因此,如果我们有一个应用程序在需要一些初始化逻辑的容器上运行,那么将此工作交给另一个容器是一个很好的做法。Kubernetes有一种用于特定作业的容器类型:init containers。

Init Containers

在Kubernetes中,init容器是在同一个Pod中的其他容器之前启动和执行的容器。它的目的是为Pod上托管的主应用程序执行初始化逻辑。例如,创建必要的用户帐户、执行数据库迁移、创建数据库模式等等。

Init Containers设计注意事项

在创建init容器时,我们应该考虑一些注意事项:

  • 它们总是比Pod里的其他容器先执行。因此,它们不应该包含需要很长时间才能完成的复杂逻辑。启动脚本通常很小而且简洁。如果我们发现在init容器中添加了太多的逻辑,那就应该考虑将它的一部分移到应用程序容器本身。
  • Init容器按顺序启动和执行。除非成功完成其前置容器,否则不会调用init容器。因此,如果启动任务很长,可以考虑将其分成若干步骤,每个步骤都由init容器处理,以便知道哪些步骤失败。
  • 如果任何init容器失败,整个Pod将重新启动(除非将restartPolicy设置为Never)。重新启动Pod意味着重新执行所有容器,包括任何init容器。因此,我们可能需要确保启动逻辑能够容忍多次执行而不会导致重复。例如,如果数据库迁移已经完成,那么应该忽略再次执行迁移命令。
  • 在一个或多个依赖项可用之前,init容器是延迟应用程序初始化的一个很好的候选者。例如,如果我们的应用程序依赖于一个施加了API请求速率限制的API,可能需要等待一段时间才能从该API接收响应。在应用程序容器中实现此逻辑可能很复杂;因为它需要与运行状况和准备状态探测相结合。一种更简单的方法是创建一个init容器,该容器等待API准备好后才能成功退出。只有在init容器成功完成其工作之后,应用程序容器才会启动。
  • Init容器不能像应用程序容器那样使用liveness和readiness探针。原因是它们注定要成功启动和退出,就像Jobs和CronJobs的行为一样。
  • 同一个Pod内的所有容器共享相同的卷和网络。我们可以使用此特性在应用程序及其init容器之间共享数据。

Init Containers的“请求”和“限制”行为

正如我们刚刚讨论的,init容器总是在同一个Pod上的其他应用程序容器之前启动。因此,调度程序为init容器的资源和限制提供了更高的优先级。这种行为必须被彻底考虑,因为它可能会导致不期望的结果。例如,如果我们有一个init容器和一个应用程序容器,并且将init容器的资源和限制设置为高于应用程序容器的资源和限制,那么只有在存在满足init容器要求的可用节点时,才会调度整个Pod。换句话说,即使有一个未使用的节点可以运行应用程序容器,如果init容器具有该节点可以处理的更高的资源先决条件,那么Pod也不会部署到该节点。因此,在定义init容器的请求和限制时,应该尽可能严格。作为最佳实践,除非绝对需要,否则不要将这些参数设置为高于应用程序容器的值。

场景01:初始化数据库

在这个场景中,我们为MySQL数据库提供服务。此数据库用于测试应用程序。它不一定要包含真实的数据,但是它必须有足够的数据种子,这样我们就可以测试应用程序的查询速度。我们使用init容器来处理下载SQL转储文件并将其还原到数据库中,该数据库托管在另一个容器中。这种情况可以说明如下:

init

yaml定义文件可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: mydb
labels:
app: db
spec:
initContainers:
- name: fetch
image: mwendler/wget
command: ["wget","--no-check-certificate","https://sample-videos.com/sql/Sample-SQL-File-1000rows.sql","-O","/docker-entrypoint-initdb.d/dump.sql"]
volumeMounts:
- mountPath: /docker-entrypoint-initdb.d
name: dump
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "example"
volumeMounts:
- mountPath: /docker-entrypoint-initdb.d
name: dump
volumes:
- emptyDir: {}
name: dump

上面的定义创建了一个Pod,它承载两个容器:init容器和application容器。让我们看看这个定义有趣的方面:

  • init容器负责下载包含数据库转储的SQL文件。我们使用mwendler/wget映像,因为我们只需要wget命令。

  • 下载的SQL的目标目录是MySQL镜像用来执行SQL文件的目录(/docker-entrypoint-initdb.d)。此行为内置到我们在应用程序容器中使用的MySQL镜像中。

  • init容器将/docker-entrypoint-initdb.d挂载到一个emptyDir卷。因为两个容器托管在同一个Pod上,所以它们共享相同的卷。因此,数据库容器可以访问emptyDir卷上的SQL文件。

如果没有Init Containers会发生什么

在这个例子中,我们使用初始化模式作为最佳实践。如果我们在不使用init模式的情况下实现相同的逻辑,那么我们必须基于mysql基本镜像创建一个新映像,安装wget,然后使用它下载SQL文件。这种方法的缺点是:

  • 如果需要对下载逻辑进行任何更改,则需要创建一个新镜像,将其推送到定义文件中并更改其引用。这增加了维护自定义镜像的负担。

  • 它在DB容器及其启动逻辑之间创建了一个紧密耦合的关系,这使得应用程序更难管理,并且增加了引入错误和bug的可能性。

场景02:延迟应用程序启动

init容器的另一个常见用例是当我们需要应用程序等待另一个服务完全运行(响应请求)时。以下定义演示了这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']

所以,假设在myapp容器上运行的应用程序必须依赖myservice正常后才能正常工作。我们需要延迟myapp直到myservice准备好。我们通过使用一个简单的nslookup命令(第11行)来实现这一点,该命令不断检查“myservice”的成功名称解析。如果nslookup能够解析“myservice”,则服务将启动。使用一个成功的退出代码,init容器终止,让位于应用程序容器开始。否则,容器将在重试之前休眠两秒钟,从而延迟应用程序容器的启动。

为了完整起见,这是myservice的定义文件:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376

写在最后

  • Init模式是设计需要启动逻辑的应用程序时必须遵循的重要实践。
  • Kubernetes提供init容器作为将应用程序逻辑与其启动过程分离的一种方法。
  • 将应用程序初始化逻辑放在init容器中有许多优点:
    • 我们将实施关注点分离原则。应用程序可以有自己的工程师团队,而其初始化逻辑由另一个团队编写。
    • 在授权和访问控制方面,拥有一个独立的团队来处理应用程序的初始化步骤,可以给公司带来更大的灵活性。例如,如果启动应用程序需要使用需要安全许可的资源(例如,修改防火墙规则),则可以由具有适当凭据的人员来完成。应用程序团队不参与操作。
    • 如果涉及太多的初始化步骤,可以将它们分解为多个init容器,然后依次执行。如果一个步骤失败,init容将报告一个错误,这将使我们更好地了解逻辑的哪一部分不成功。
  • 在使用init容器时,应该考虑以下几点:
    • 初始化容器在失败时重新启动。因此,它们的代码必须是幂等的。
    • Init容器的请求和限制会先被调度程序用于调度判断。错误的值可能会对调度器决定将整个Pod(包括应用程序容器)放置在哪里产生负面影响。

Kubernetes中的InitContainer资源是一个有趣且非常有用的资源。在许多情况下,我们会看到它曾用于在Pod部署时,创建容器之前在卷中预填充数据,因此在业务容器启动时,卷数据已被初始化。

就我而言,我有一个带有单个静态页面的简单Web前端,它使用标准的nginx基础镜像:

1
2
3
4
FROM nginx

COPY index.html /usr/share/nginx/html/index.html
COPY smartos.ipxe /usr/share/nginx/html/smartos.ipxe

该镜像的构建和下载速度非常快,这非常棒,但是部分原因是它是无状态的。例如,smartos.ipxe文件中需要一些数据,这些数据在启动应用程序时需要可用,否则这些引用将无法按预期工作(抽象为404 HTTP响应):

1
2
3
4
5
6
#!ipxe
dhcp
set base-url http://sdc-ipxe.east.gourmet.yoga
kernel ${base-url}/smartos/smartos/platform/i86pc/kernel/amd64/unix -B smartos=true,console=ttyb,ttyb-mode="115200,8,n,1,-"
module ${base-url}/smartos/smartos/platform/i86pc/amd64/boot_archive type=rootfs name=ramdisk
boot

但是,这些文件不是应用程序的一部分,因为它们经常更新。因此,每次推出新版本时,我们都希望该卷中包含最新版本,并且由于我们不需要维护镜像中的这些文件,否则在我们的Registry中存储起来会非常大且昂贵,我们可以在Pod中的容器上挂载一个Volume来提供它们。

因此,基本上,我们需要一种方法来预填充要装入到/usr/share/nginx/html/smartos的卷。

使用InitContainer资源,我们可以指定要运行的命令,并且像Pod中的任何其他容器一样,我们可以分配要挂载的卷,因此让我们从这样的Kubernetes清单开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: Deployment
metadata:
name: sdc-ipxe-deployment
labels:
app: sdc-ipxe
spec:
replicas: 2
selector:
matchLabels:
app: sdc-ipxe
template:
metadata:
labels:
app: sdc-ipxe
spec:
initContainers:
- name: config-data
image: ubuntu:xenial
command: ["/bin/sh","-c"]
args: ["apt update; apt install -y wget tar; wget https://us-east.manta.joyent.com/Joyent_Dev/public/SmartOS/platform-latest.tgz; tar xvf platform-latest.tgz -C /data; mkdir /data/smartos; mv /data/platform* /data/smartos/platform"]
volumeMounts:
- mountPath: /data
name: sdc-data
volumes:
- name: sdc-data
hostPath:
path: /mnt/kube-data/sdc-ipxe/
type: DirectoryOrCreate

因此,在这一点上,我们正在准备卷sdc数据,将其挂载到initContainer的/data目录上并运行:

1
apt update; apt install -y wget tar; wget https://us-east.manta.joyent.com/Joyent_Dev/public/SmartOS/platform-latest.tgz; tar xvf platform-latest.tgz -C /data; mkdir /data/smartos; mv /data/platform* /data/smartos/platform

上述命令下载数据并将其提取到卷中。现在,我们向yaml中添加一个container,然后再次附加该Volume,将可以使用预填充的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: Deployment
metadata:
name: sdc-ipxe-deployment
labels:
app: sdc-ipxe
...
containers:
- name: sdc-ipxe
image: coolregistryusa.bix/jmarhee/sdc-ipxe:latest
imagePullPolicy: Always
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html/smartos
name: sdc-data
...

在业务容器中配置相同名称的卷,则业务容器就可以通过/usr/share/nginx/html/smartos目录获取sdc数据。

如果我们的应用程序依赖于具有可变需求的配置,则这种模式是有用的。可能是我们需要获得令牌,或者地址是动态的,并且需要通过磁盘上的文件而不是环境(比如负载平衡器,Web服务器或具有配置文件的数据库客户端,不容易通过它处理)传递文件(因为它们更改的频率不同)(Secret或ConfigMap),这种方法提供了一个易于编程的界面,用于预先填充或完成传递给容器的数据的模板化。

由于Docker在Kubernetes v1.20中已弃用,最近几天在Twitter上发生了很多讨论。

问题背景

Kubernetes v1.20的废弃说明:

如果想了解更多,强烈建议查看此Twitter

考虑到最近部署了一个Raspberry Pi Kubernetes集群,因此想就地进行更新,以使用Containerd代替Docker作为容器运行时。

免责声明–不要在生产集群中这样做。对于这些集群,只需删除现有节点,然后滚动引入新节点。这个博客只是关于Raspberry Pi集群的一个有趣的话题,看看是否可以在无需重建节点的情况下就地完成更新。

因此,要做的第一件事是drain需要更新的节点(我的节点称为k8s-node-1)并且cordon它:

1
kubectl drain k8s-node-1 --ignore-daemonsets

然后ssh进入节点并停止kubelet:

1
systemctl stop kubelet

然后删除Docker:

1
apt-get remove docker.io

删除旧的依赖项:

1
apt-get autoremove

现在unmask现有的containerd服务(Docker使用containerd,这就是为什么它已经存在的原因):

1
systemctl unmask containerd

安装所需的依赖项:

1
apt-get install unzip make golang-go libseccomp2 libseccomp-dev btrfs-progs libbtrfs-dev

完成以上步骤后,现在我们按照官方说明开始安装containerd。

无论如何,以root身份进行所有操作,获取containerd的源代码:

1
go get -d github.com/containerd/containerd

获取protoc 并安装:

1
2
wget -c https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
sudo unzip protoc-3.11.4-linux-x86_64.zip -d /usr/local

获取runc 的源代码:

1
go get -d github.com/opencontainers/runc

进入到下载的包目录(检查$ GOPATH变量),使用make进行构建和安装runc和containerd:

1
2
3
4
5
6
cd ~/go/src/github.com/opencontainers/runc
make
make install
cd ~/go/src/github.com/containerd/containerd
make
make install

现在,将containerd.service文件复制到systemd以创建containerd的服务、启动服务并查看启动状态:

1
2
3
4
5
6
cp containerd.service /etc/systemd/system/
chmod 644 /etc/systemd/system/containerd.service
systemctl daemon-reload
systemctl start containerd
systemctl enable containerd
systemctl status containerd

差不多完成了,现在我们需要更新kubelet,将默认使用的docker改为containerd(参考flag设置)。我们可以通过运行:

1
sed -i 's/3.2/3.2 --container-runtime=remote --container-runtime-endpoint=unix:\/\/\/run\/containerd\/containerd.sock/g' /var/lib/kubelet/kubeadm-flags.env

如果上面的命令不起作用,请直接修改kubeadm-flags.env文件。

重启kubelet并查看服务状态:

1
2
systemctl start kubelet
systemctl status kubelet

最后,uncordon节点,并查看节点信息中的Runtime已变为containerd:

1
2
kubectl uncordon k8s-node-1
kubectl get nodes -o wide

问题引出

Helm是将应用程序部署到Kubernetes的绝佳工具。我们可以打包所有deployment和service等yaml文件,并使用一个简单的命令将它们部署到集群中。

但是Helm的另一个非常酷的功能是能够轻松升级和回滚版本(在集群中运行的Helm Chart实例的术语)的功能。

现在,我们可以使用kubectl进行此操作。如果我们使用kubectl apply升级deployment资源,则可以使用kubectl rollout undo来回滚该升级。这很棒!这是Kubernetes的最佳功能之一。

升级deployment时,将为该deployment创建一个新的replicaset,该replicaset将在一组新的Pod中运行升级后的应用程序。

如果使用kubectl rollout undo进行回滚,会删除最新replicaset中的容器,并回滚到旧replicaset的容器。

但是这里有一个潜在的问题。如果删除旧的replicaset会怎样?如果发生这种情况,我们将无法回滚升级。好吧,我们无法使用kubectl rollout undo将其回滚,但是如果我们使用Helm,会发生什么?

让我们来看一个演示。

Helm环境准备

创建一个称为testchart的Chart:

1
helm create testchart	

删除模板目录中所有不必要的文件:

1
rm -rf ./testchart/templates/*

创建一个deployment yaml文件:

1
2
3
4
kubectl create deployment nginx \
--image=nginx:1.17 \
--dry-run=client \
--output=yaml > ./testchart/templates/deployment.yaml

这将创建以下yaml并将其另存为templates目录中的deployment.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.17
name: nginx
resources: {}
status: {}

创建deployment:

1
kubectl create deployment nginx --image=nginx:1.17 

为service生成yaml:

1
2
3
4
5
kubectl expose deployment nginx \
--type=LoadBalancer \
--port=80 \
--dry-run=client \
--output=yaml > ./testchart/templates/service.yaml

这将为我们提供以下yaml并将其另存为模板目录中的service.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
status:
loadBalancer: {}

删除deployment,模板化values.yaml 和deployment.yaml文件:

1
2
3
4
kubectl delete deployment nginx
rm ./testchart/values.yaml
echo "containerImage: nginx:1.17" > ./testchart/values.yaml
sed -i 's/nginx:1.17/{{ .Values.containerImage }}/g' ./testchart/templates/deployment.yaml

最终,deployment.yaml文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: {{ .Values.containerImage }}
name: nginx
resources: {}
status: {}

改造后的yaml中容器镜像不再是硬编码的。它将从values.yaml文件中获取nginx:1.17的值,或者我们可以使用set标志来覆盖它(我们将在一分钟内完成)。

Helm部署示例

首先,将Chart部署到Kubernetes集群中:

1
helm install testchart ./testchart

helm-install-1

该应用程序版本是Chart.yaml文件中设置的默认版本(尚未更新)

检查部署中运行的镜像版本:

1
kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'

get-container-image-1

查看到的容器镜像就是Chart中values.yaml文件中定义的镜像版本。

现在升级Release,将默认的容器镜像值替换为set标志指定的值:

1
helm upgrade testchart ./testchart --set containerImage=nginx:1.18

确认版本已升级(检查版本号):

1
helm list

helm-upgrade-1

另外,请确认Release历史:

1
helm history testchart

helm-history-updated

这样我们就可以看到该Release的初始部署,然后是升级。应用版本保持不变,因为我没有更改Chart.yaml文件中的值。但是,镜像版本已更改,我们可以通过以下方式看到:

1
kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'

get-container-image-2

因此,我们已经升级了在deployment中容器运行的镜像版本。

让我们看一下deployment的replicasets:

1
kubectl get replicasets

get-replicasets-1

因此,我们为Helm版本创建的deployment有两个replicasets。最初的一个运行nginx v1.17,最新的一个运行nginx v1.18。

如果我们想使用kubectl回退升级,则可以使用(不要运行此代码!):

1
kubectl rollout undo deployment nginx

kubectl-rollout-undo

这里将发生的是,删除最新replicasets下的Pod,并创建旧replicasets下的Pod,将nginx回滚到v1.17。

但是我们不会那样做,因为我们正在使用Helm。

问题复现

继续在当前环境中获取最旧的replicasets名称,并删除它::

1
2
REPLICA_SET=$(kubectl get replicasets -o jsonpath='{.items[0].metadata.name }' --sort-by=.metadata.creationTimestamp)
kubectl delete replicasets $REPLICA_SET

因此,我们现在只有一个replicasets:

1
kubectl get replicasets

get-replicasets-2

现在尝试使用kubectl rollout undo命令进行回滚:

1
kubectl rollout undo deployment nginx

kubectl-rollout-undo-2

失败的原因是我们删除了旧的replicasets,因此该deployment没有历史记录,可以通过以下方式查看:

1
kubectl rollout history deployment nginx

kubectl-rollout-history

使用Helm回滚

虽然旧的replicasets被删除了,但是Helm的实现机制决定了使用Helm部署的Release会保留历史:

1
helm history testchart

helm-history-2

所以,我们可以使用Helm回滚:

1
helm rollback testchart 1

helm-rollback

查看Release状态:

1
helm list

helm-list-rollback

查看Release历史:

1
helm history testchart

helm-rollback-history

查看replicasets:

1
kubectl get replicasets

get-replicasets-3

旧的replicasets又回来了!怎么样?

原理探究

让我们看一下集群中的secrets:

1
kubectl get secrets

kubectl-get-secrets

可以看出,这些secrets中会存储Helm发布所有历史记录!初始版本(v1),升级(v2)和回滚(v3)。

让我们仔细看看v1版本:

1
kubectl get secret sh.helm.release.v1.testchart.v1 -o json

kubectl-get-secrets-2

嗯,这个Release内容看起来很有趣。我们可以做的是对base64进行解码,然后通过http://www.txtwizard.net/compression进行解压缩,得到结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
"name":"testchart",
"info":
{
"first_deployed":"2020-08-09T11:21:20.4665817+01:00",
"last_deployed":"2020-08-09T11:21:20.4665817+01:00",
"deleted":"",
"description":"Install complete",
"status":"superseded"},
"chart":{"metadata":
{
"name":"testchart",
"version":"0.1.0",
"description":"A Helm chart for Kubernetes",
"apiVersion":"v2",
"appVersion":"1.16.0",
"type":"application"},
"lock":null,
"templates":[
{
"name":
"templates/deployment.yaml",
"data":"YXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIGNyZWF0aW9uVGltZXN0YW1wOiBudWxsCiAgbGFiZWxzOgogICAgYXBwOiBuZ2lueAogIG5hbWU6IG5naW54CnNwZWM6CiAgcmVwbGljYXM6IDEKICBzZWxlY3RvcjoKICAgIG1hdGNoTGFiZWxzOgogICAgICBhcHA6IG5naW54CiAgc3RyYXRlZ3k6IHt9CiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgY3JlYXRpb25UaW1lc3RhbXA6IG51bGwKICAgICAgbGFiZWxzOgogICAgICAgIGFwcDogbmdpbngKICAgIHNwZWM6CiAgICAgIGNvbnRhaW5lcnM6CiAgICAgIC0gaW1hZ2U6IHt7IC5WYWx1ZXMuY29udGFpbmVySW1hZ2UgfX0KICAgICAgICBuYW1lOiBuZ2lueAogICAgICAgIHJlc291cmNlczoge30Kc3RhdHVzOiB7fQo="},{"name":"templates/service.yaml","data":"YXBpVmVyc2lvbjogdjEKa2luZDogU2VydmljZQptZXRhZGF0YToKICBjcmVhdGlvblRpbWVzdGFtcDogbnVsbAogIGxhYmVsczoKICAgIGFwcDogbmdpbngKICBuYW1lOiBuZ2lueApzcGVjOgogIHBvcnRzOgogIC0gcG9ydDogODAKICAgIHByb3RvY29sOiBUQ1AKICAgIHRhcmdldFBvcnQ6IDgwCiAgc2VsZWN0b3I6CiAgICBhcHA6IG5naW54CiAgdHlwZTogTG9hZEJhbGFuY2VyCnN0YXR1czoKICBsb2FkQmFsYW5jZXI6IHt9Cg=="}],"values":{"containerImage":"nginx:1.17"},"schema":null,"files":[{"name":".helmignore","data":"IyBQYXR0ZXJucyB0byBpZ25vcmUgd2hlbiBidWlsZGluZyBwYWNrYWdlcy4KIyBUaGlzIHN1cHBvcnRzIHNoZWxsIGdsb2IgbWF0Y2hpbmcsIHJlbGF0aXZlIHBhdGggbWF0Y2hpbmcsIGFuZAojIG5lZ2F0aW9uIChwcmVmaXhlZCB3aXRoICEpLiBPbmx5IG9uZSBwYXR0ZXJuIHBlciBsaW5lLgouRFNfU3RvcmUKIyBDb21tb24gVkNTIGRpcnMKLmdpdC8KLmdpdGlnbm9yZQouYnpyLwouYnpyaWdub3JlCi5oZy8KLmhnaWdub3JlCi5zdm4vCiMgQ29tbW9uIGJhY2t1cCBmaWxlcwoqLnN3cAoqLmJhawoqLnRtcAoqLm9yaWcKKn4KIyBWYXJpb3VzIElERXMKLnByb2plY3QKLmlkZWEvCioudG1wcm9qCi52c2NvZGUvCg=="}]},
"manifest":"---\n#
Source: testchart/templates/service.yaml\n
apiVersion: v1\n
kind: Service\nmetadata:\n
creationTimestamp: null\n
labels:\n
app: nginx\n
name: nginx\n
spec:\n
ports:\n
- port: 80\n
protocol: TCP\n
targetPort: 80\n
selector:\n
app: nginx\n
type: LoadBalancer\n
status:\n loadBalancer: {}\n---\n#

Source: testchart/templates/deployment.yaml\n
apiVersion: apps/v1\n
kind: Deployment\n
metadata:\n
creationTimestamp: null\n
labels:\n
app: nginx\n
name: nginx\nspec:\n
replicas: 1\n
selector:\n
matchLabels:\n
app: nginx\n
strategy: {}\n
template:\n
metadata:\n
creationTimestamp: null\n
labels:\n
app: nginx\n
spec:\n
containers:\n
- image: nginx:1.17\n
name: nginx\n
resources: {}\n
status: {}\n",
"version":1,
"namespace":"default"
}

BOOM!看起来就像我们的deployment和service清单!我们可以看到最初的Helm版本中包含的所有信息(确认容器镜像为nginx:1.17)!

因此,通过将这些信息作为secrets存储在目标Kubernetes集群中,即使已删除了旧的replicasets,Helm也可以回滚升级!太酷了!

不过结果还不是很清晰,查看data字段……看起来像是加密信息。

让我们解密吧!这次在命令行上:

1
kubectl get secret sh.helm.release.v1.testchart.v1 -o jsonpath="{ .data.release }" | base64 -d | gunzip -c | jq '.chart.templates[].data' | tr -d '"' | base64 -d

decode-helm-secret

哈!这里有deployment和service的yaml文件!

通过使用Helm,即使已删除deployment的旧replicasets,我们也可以回滚,因为Helm将Release历史记录在secrets并存储在目标Kubernetes集群中。通过使用上面的代码,我们可以解密这些secrets并查看其中包含的信息。

如果我们在集群中安装了Helm Chart,可能会想知道Release的存储位置。

背景知识

让我们从一些背景开始。安装一个简单的Nginx Helm Chart:

1
$ helm install --name my-release stable/nginx-ingress

现在,要获取已安装Helm的详细信息,可以使用四个命令。

helm ls

1
2
3
$ helm ls 
NAME REVISION UPDATED STATUS
my-release 1 Wed Sep 12 07:41:48 2018 DEPLOYED

通常,我们要运行的第一个命令是helm ls。执行此操作是为了了解我们的集群中当前安装了哪些Helm Chart。无论它们是否失败,STATUS会展示出部署结果是成功还是失败。

helm get

一旦获得安装Chart的名称。下一步通常是尝试更详细地了解安装了什么。helm get命令可以为我们提供帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ helm get my-release
REVISION: 1
RELEASED: Thu Mar 23 15:59:14 2017
CHART: nginx-1.0
USER-SUPPLIED VALUES:
foo: bar

COMPUTED VALUES:
foo: bar
image: nginx
imagePullPolicy: IfNotPresent
ingress:
# **....**

helm status

如果我们遇到任何问题,并且希望获得Chart开发人员写下的一些说明。helm status可以通过呈现NOTES.txt文件来帮助我们。

1
2
3
4
5
6
$ helm status my-release
The nginx-ingress controller has been installed.
Get the application URL by running these commands:
export NODE_IP=$(kubectl --namespace {{ .Release.Namespace }} get nodes -o jsonpath="{.items[0].status.addresses[1].address}")
echo "Visit http://10.10.10.10:80 to access your application via HTTP."
echo "Visit https://10.10.10.10:443 to access your application via HTTPS."

上面的Helm状态可以通过values.yaml或–set修改。这是从NOTES.txt呈现的帮助者文本。

helm history

最后,我们还可以获得Chart部署的修订历史记录。当运行helm upgrade命令时会更新版本。假设我们要使用override.yaml覆盖某些值。

1
2
3
4
5
$ helm upgrade --install my-release --values override.yaml --set foo=notbar nginx 
$ helm history my-release
REVISION UPDATED STATUS CHART DESCRIPTION
1 Thu Mar 23 15:57:40 2020 SUPERSEDED nginx-0.4.3 Install complete
2 Thu Mar 23 15:59:14 2020 DEPLOYED nginx-0.4.3 Upgrade complete

所有这些信息都存储在哪里?

  • Helm v2版本,默认位置在configmap中:

    1
    2
    3
    4
    $ kubectl get configmap -n kube-system -l "OWNER=TILLER"
    NAME DATA AGE
    my-release.v1 1 7m
    my-release.v2 1 6m
  • Helm v3版本,默认位置在secrets中。强烈建议这样做,因为这些数据包含许多有关我们部署的信息:

    1
    2
    3
    4
    5
    $ kubectl get secrets -n kube-system
    NAME DATA AGE
    my-release.v1 1 7m
    my-release.v2 1 6m
    default-token-43hfuds 1 1d

解析Configmap内容

步骤1. 获取Configmap数据:

1
$ kubectl get configmap -n kube-system my-release.v1 -o=jsonpath='{.data.release}' > release-encoded

步骤2. 确保编码后的Release包含如下字符串:

1
2
H4sIAAAAAAAC/+w6TY8cS.....
# you should see a long block of string like above

步骤3. 解析数据:

1
cat release-encoded | base64 -d | gzip -cd > release-decoded

步骤4. 查看数据:

1
2
3
4
cat release-decoded
# you should see a whole bunch of data for the chart similar to above when you did helm get.
# but also this data contains a lot more like. the actual template. Value rendered.. etc...
# try it :) i already gave you the commands 🤠⽕😁🏃🏼‍

将Chart存储在configmaps中的问题在于,一旦黑客进入我们的集群,它就会成为黑客的金钥匙。将其存储为secrets可以提供某种保护(假设我们对机密信息进行了加密)。☸️

解析Secrets内容

步骤1. 解析指定版本的Release所有内容(Template内容依然是编码格式):

1
kubectl get secret sh.helm.release.v1.my-release.v1 -o jsonpath="{ .data.release }" | base64 -d | gunzip -c | jq .

步骤2. 解析指定版本的Release中的Template内容:

1
kubectl get secret sh.helm.release.v1.my-release.v1 -o jsonpath="{ .data.release }" | base64 -d | gunzip -c | jq '.chart.templates[].data' | tr -d '"' | base64 -d

注:该方法同样适用于解析Configmap内容。

一些建议

还有一些保护tiller的方法,例如使用https连接。但是,按照设计,tiller仍然需要大量特权才能在我们的集群中运行。并且仍然违反最小特权原则我的建议是尽快移至helm3

Helm3完全删除了tiller,而是依靠本地计算机的身份验证在群集中工作。默认情况下,它还将Chart数据作为secrets存储在群集中。Helm2将在2020年12月停止提供安全修复程序

本文提供了在Kubernetes上部署安全,可伸缩和弹性服务的最佳实践。内容开源在github仓库。如果有缺少或不足之处,欢迎提issue。

Part1 应用开发

健康检测

  • 为容器配置Readiness探针
    • 如果未设置readiness探针,则kubelet会假定该应用程序已准备就绪,可以在容器启动后立即接收流量。
    • 如果容器需要2分钟才能启动,则这2分钟内对容器的所有请求将失败。
  • 发生致命错误时允许容器崩溃
    • 如果应用程序遇到不可恢复的错误,则应使其崩溃。

    • 此类不可恢复的错误的示例是:

      1. 未捕获的异常
      2. 代码中的错字(动态语言)
      3. 无法加载标头或依赖项
    • 上述错误不应发信号通知Liveness探针失败。相反,应该立即退出该进程,并让kubelet重新启动容器。

  • 配置被动的Liveness探针
    • Liveness探针旨在容器卡住时重新启动容器。
    • 考虑以下情形:如果应用程序正在处理无限循环,则无法退出。当该进程消耗100%的CPU时,将没有时间回复(其他)Readiness探针检查,并且最终将其从服务中删除。但是,该Pod仍被注册为当前Deployment的活动副本。如果没有Liveness探针,它将保持运行状态,但与服务分离。换句话说,该进程不仅不处理任何请求,而且也在消耗资源。
    • 请注意,不应该使用Liveness探针来处理应用程序中的致命错误,并要求Kubernetes重新启动应用程序。相反,应该让应用程序崩溃。仅在过程无响应的情况下,才应将“liveness”探针用作恢复机制。
  • 两个探针的值不同
    • 当“liveness”和“readiness”探针指向相同的端点时,探针的作用会合并在一起。当应用程序发出信号表明尚未准备就绪或尚待运行时,kubelet会将容器与服务分离并同时将其删除。这时可能会注意到连接断开,因为容器没有足够的时间耗尽当前连接或处理传入的连接。
    • 更多信息参考:handling-client-requests-properly-with-kubernetes/

请注意,readiness和liveness没有默认值。

应用独立

  • Readiness探针是独立的

    • Readiness不包括对服务的依赖性,例如:数据库、数据库的迁移、API、第三方服务(反例
  • 应用重试连接到依赖服务

    • 应用启动时,它不应该因为数据库等依赖项尚未就绪而崩溃。相反,应用程序应继续尝试重新连接数据库,直到成功为止。
    • Kubernetes希望可以以任何顺序启动应用程序。当确保应用程序可以重新连接到诸如数据库之类的依赖项时,便知道可以提供更强大,更灵活的服务。

友好关闭

  • 应用程序未通过SIGTERM关闭,但可以正常终止连接
    • 可能需要一些时间才能感知到诸如kube-proxy或Ingress控制器之类的组件endpoint更改。因此,尽管标记为已终止,流量仍可能流向Pod。
    • 应用程序应停止在所有剩余连接上接受新请求,并在耗尽传出队列后将其关闭。
    • 如果想回顾endpoint在群集中的传播方式,请参考:handling-client-requests-properly-with-kubernetes/
  • 应用程序仍在宽限期内处理传入的请求
    • 可能要考虑使用容器生命周期事件(例如preStop处理程序)来自定义Pod删除之前发生的情况。
  • Dockerfile中的CMD将SIGTERM转发到进程
  • 关闭所有空闲的keep-alive套接字
    • 如果应用程序调用未关闭TCP连接(例如使用TCP保持活动状态或连接池),它将连接到一个Pod,而不使用该服务中的其他Pod。
    • 不应该突然终止长期存在的连接。相反,应该在关闭应用程序之前终止它们。
    • 更多信息参考:gracefully-shutting-down-a-nodejs-http-server

失败容忍

  • 为Deployment部署运行多个副本
    • 切勿单独运行一个Pod类型的资源,而是考虑将Pod作为Deployment,DaemonSet,ReplicaSet或StatefulSet的一部分进行部署。
    • 示例参考:Node-Management-In-GKE
  • 避免将Pod放置在单个节点中
    • 即使运行Pod的多个副本,也无法保证丢失节点不会影响服务。
    • 应该将反关联性规则应用于部署,以便Pod分布在群集的所有节点中。
    • 更多信息参考:inter-pod-affinity-and-anti-affinit
  • 设定Pod中断预算
    • drain节点后,该节点上的所有Pod都将被删除并重新安排。
    • 为了保护Deployment免受可能同时摧毁多个Pod的意外事件的影响,可以定义Pod中断预算。
    • 更多信息参考:pod-disruptions

资源使用

  • 为所有容器设置内存限制和请求
    • 资源限制用于限制容器可以使用多少CPU和内存,并使用containerSpec的resources属性设置。
    • 调度程序将这些用作度量标准之一,以确定哪个节点最适合当前Pod。
    • 根据调度程序,没有内存限制的容器的内存利用率为零。
    • 如果可调度在任何节点上的Pod数量不受限制,则会导致资源超负荷使用并可能导致节点(和kubelet)崩溃。
    • 如果容器进程超出内存限制,则该进程将终止。由于CPU是可压缩的资源,因此如果容器超出限制,则将限制该过程。即使它可以使用当时可用的某些CPU。
    • 更多信息参考:understanding-resource-limits-in-kubernetes-memoryunderstanding-resource-limits-in-kubernetes-cpu
  • 将CPU请求设置为1个CPU或以下
    • 除非有计算密集型作业,否则建议将请求设置为1个CPU或更低.
    • 更多信息参考:YouTube视频
  • 禁用CPU限制—除非有很好的用例
    • CPU 资源以 CPU 单位度量。
    • cpu:1表示每秒1个CPU单位。如果有1个线程,则每秒消耗的CPU时间不能超过1秒。如果有2个线程,则可以在0.5秒内消耗1个CPU单位。8个线程可以在0.125秒内消耗1个CPU单位。此后,请求将受到限制。
    • 如果不确定最佳应用设置,最好不要设置CPU限制。
    • 更多信息参考:understanding-resource-limits-in-kubernetes-cpu
  • 命名空间具有LimitRange
    • 如果我们认为可能忘记设置内存和CPU限制,则应考虑使用LimitRange对象为当前名称空间中部署的容器定义标准大小。
    • 设置方法参考:limit-range
  • 为Pod设置适当的服务质量(QoS)
    • 当节点进入过量使用状态(即使用过多资源)时,Kubernetes会尝试驱逐该节点中的某些Pod。
    • Kubernetes根据定义明确的逻辑对Pod进行排名和逐出。
    • 设置方法参考:quality-service-pod

请注意,如果不确定如何配置正确的CPU或内存限制,则可以使用Kubernetes中的Vertical Pod Autoscaler。自动缩放器会分析应用并给出建议的值。

标签资源

  • 定义技术标签
  • 定义业务标签
  • 定义安全标签

日志配置

  • 将应用程序日志记录到stdout和stderr
    • 有两种日志记录策略:被动和主动。使用被动日志记录的应用程序不了解日志记录基础结构,而是将消息记录到标准输出中。
    • 在主动日志记录中,该应用程序与中间聚合器建立网络连接,将数据发送到第三方日志记录服务,或直接写入数据库或索引。主动日志记录被视为反模式,应避免使用它。
    • 最佳实践参考:logs
  • 避免使用sidecar记录日志(如果可以的话)
    • 如果希望将日志转换应用于具有非标准日志事件模型的应用程序,则可能需要使用sidecar容器。
    • 使用Sidecar容器,可以在将日志条目运送到其他地方之前对其进行规范化。例如,先将Apache日志转换为Logstash JSON格式,然后再将其发送到日志基础结构。但是,如果可以控制应用程序,则可以从一开始就输出正确的格式。这样可以节省为集群中的每个Pod运行额外的容器的时间。

Pod扩缩容

  • 容器在其本地文件系统中不存储任何状态
    • 容器可以访问本地文件系统,用户可能会想使用它来持久化数据。
    • 但是,将持久性数据存储在容器的本地文件系统中会阻止Pod进行水平缩放(即通过添加或删除Pod的副本)。
    • 这是因为,通过使用本地文件系统,每个容器都维护自己的“状态”,这意味着Pod副本的状态可能会随时间而变化。从用户的角度来看,这会导致行为不一致(例如,当请求命中一个Pod时,一条特定的用户信息可用,但当请求命中另一个Pod时,则不可用)。
    • 相反,任何持久性信息都应保存在Pod外部的集中位置。例如,在集群中的PersistentVolume中,或者在集群外部的某些存储服务中甚至更好。
  • 对具有可变使用模式的应用程序使用HPA
    • HPA是内置的Kubernetes功能,可监视应用程序并根据当前使用情况自动添加或删除Pod副本。
    • 配置HPA可使应用在任何流量情况下(包括意外的高峰)保持可用并响应。
    • 配置HPA时必须创建一个HorizontalPodAutoscaler资源,该资源定义要监视的应用程序的度量。
    • HPA可以监视内置资源指标(Pod的CPU和内存使用情况)或自定义指标。对于自定义指标,还负责收集和公开这些指标,例如,可以使用Prometheus和Prometheus Adapter进行此操作。
  • Vertical Pod Autoscaler仍处于Beta版,请勿使用
    • 类似于HPA,还有VPA。
    • VPA可以自动调整Pod的资源请求和限制,以便当Pod需要更多资源时可以获取它们(增加/减少单个Pod的资源称为垂直缩放,与水平缩放相对)。
    • 这对于缩放无法水平缩放的应用程序很有用。
    • 但是,HPA当前处于beta版本,它具有一些已知的局限性(例如,通过更改其资源要求来扩展Pod,要求终止Pod并重新启动它)。
    • 考虑到这些限制以及Kubernetes上大多数应用程序都可以水平扩展的事实,建议不要在生产环境中使用VPA(至少要等到稳定的版本才能使用)。
  • 如果工作负载差异很大,请使用群集自动伸缩放器
    • 群集自动缩放器是“自动缩放器”的另一种类型(HAP和VPA除外)。
    • 群集自动缩放器可以通过添加或删除工作节点来自动缩放群集的大小。
    • 当由于现有工作节点上的资源不足而无法调度Pod时,会进行放大操作。在这种情况下,Cluster Autoscaler将创建一个新的工作节点,以便可以调度Pod。同样,当现有工作节点的利用率较低时,群集自动伸缩程序可以通过从一个工作节点中逐出所有工作负载并将其删除来进行缩减。
    • 对于高度可变的工作负载,例如当Pods的数量可能在短时间内成倍增长然后返回到先前的值时,使用Cluster Autoscaler是有意义的。在这种情况下,群集自动伸缩器可以满足需求高峰,而不会通过过度配置工作节点来浪费资源。
    • 但是,如果工作负载变化不大,则可能不值得设置Cluster Autoscaler,因为它可能永远不会触发。如果工作负载缓慢且单调地增长,则足以监视现有工作节点的利用率并在达到临界值时手动添加其他工作节点。

配置原则

  • 外部化所有配置
    • 配置应在应用程序代码之外进行维护。
    • 这有几个好处。首先,更改配置不需要重新编译应用程序。其次,可以在应用程序运行时更新配置。第三,相同的代码可以在不同的环境中使用。
    • 在Kubernetes中,可以将配置保存在ConfigMaps中,然后可以在将卷作为环境变量传入时将其安装到容器中。
    • 在ConfigMap中仅保存非敏感配置。对于敏感信息(例如凭据),请使用Secret资源。
  • 将Secrets作为卷而不是环境变量安装
    • Secret资源的内容应作为卷装入容器中,而不应作为环境变量传递。
    • 这是为了防止秘密值出现在用于启动容器的命令中,该命令可能由不应该访问秘密值的人员看到。

Part2 集群管理

命名空间限制

  • 命名空间具有LimitRange
    • 没有限制的容器可能导致与其他容器的资源争用以及计算资源的消耗。
    • Kubernetes具有两个限制资源利用的功能:ResourceQuota和LimitRange。
    • 使用LimitRange对象,可以定义资源请求的默认值以及名称空间内单个容器的限制。
    • 在该命名空间内创建的,未明确指定请求和限制值的任何容器都将分配为默认值。
    • 更多信息参考:resource-quotas
  • 命名空间具有ResourceQuotas
    • 使用ResourceQuotas,可以限制命名空间内所有容器的总资源消耗。
    • 定义命名空间的资源配额会限制属于该名称空间的所有容器可以消耗的CPU,内存或存储资源的总量。
    • 还可以为其他Kubernetes对象设置配额,例如当前名称空间中的Pod数量。
    • 如果存在他人使用群集并创建20000 ConfigMap,则可以使用LimitRange来防止这种情况。

Pod安全策略

  • 启用Pod安全策略

    • 例如,可以使用Kubernetes Pod安全策略来限制:
      1. 访问主机进程或网络名称空间;
      2. 运行特权容器容器;
      3. 运行的用户;
      4. 访问主机文件系统;
      5. Linux功能,Seccomp或SELinux配置文件
    • 选择正确的策略取决于集群的性质。
    • 更多信息参考:kubernetes-pod-security-policy
  • 禁用特权容器

    • 在Pod中,容器可以以“特权”模式运行,并且对主机系统上的资源的访问几乎不受限制。
    • 尽管在某些特定的用例中,必须具有这种级别的访问权限,但总的来说,让容器执行此操作存在安全风险。
    • 特权Pod的有效使用案例包括在节点上使用硬件,例如GPU。
    • 更多信息参考:security-context
  • 在容器中使用只读文件系统

    • 在容器中运行只读文件系统会强制容器不可变。
    • 这不仅减轻了一些旧的(且有风险的)做法(例如热修补),而且还帮助防止了恶意进程在容器内存储或操作数据的风险。
    • 使用只读文件系统运行容器听起来可能很简单,但是可能会带来一些复杂性。
    • 如果需要写日志或将文件存储在临时文件夹中怎么办?
    • 更多信息参考:running-docker-containers-securely-in-production
  • 防止容器以root身份运行

    • 在容器中运行的进程与主机上的任何其他进程没有什么不同,只不过它有一小部分元数据声明它在容器中。
    • 因此,容器中的根与主机上的根(uid 0)相同。
    • 如果用户设法脱离了以root用户身份在容器中运行的应用程序,则他们可能能够使用同一root用户获得对主机的访问权限。
    • 配置容器以使用非特权用户是防止特权升级攻击的最佳方法。
    • 更多信息参考:processes-in-containers-should-not-run-as-root
  • 限制capabilities

    • Linux capabilities使进程能够执行许多特权操作,其中只有root用户默认可以执行。

    • 例如,CAP_CHOWN允许进程“对文件UID和GID进行任意更改”。

    • 即使进程不是以root身份运行,进程也有可能通过提升特权来使用那些类似root的功能。

    • 换句话说,如果不想受到损害,则应仅启用所需的功能。

    • 但是应该启用什么功能?为什么?以下两篇文章探讨了有关Linux内核功能的理论和最佳实践:

      Linux Capabilities: Why They Exist and How They Work

      Linux Capabilities In Practice

  • 防止特权升级

    • 应该在关闭特权升级的情况下运行容器,以防止使用setuid或setgid二进制文件提升特权。

网络策略

  • 启用网络策略
    • Kubernetes网络策略指定Pod组的访问权限,就像云中的安全组用于控制对VM实例的访问一样。
    • 换句话说,它在Kubernetes集群上运行的Pod之间创建了防火墙。
    • 更多信息参考:Securing Kubernetes Cluster Networking
  • 每个命名空间中都有一个保守的NetworkPolicy

RBAC策略

  • 禁用默认服务帐户的自动挂载RBAC策略
  • 设置为所需的最少特权
    • 寻找有关如何设置RBAC规则的好的建议是一项挑战。
    • Kubernetes RBAC的3种现实方法中,可以找到三种实用场景和有关如何入门的实用建议。
  • RBAC策略是精细的,不能共享
    • Zalando有一个简洁的策略来定义角色和ServiceAccounts。
    • 首先,他们描述他们的要求:
      1. 用户应该能够部署,但不应允许他们查看如“secret”这类资源
      2. 管理员应拥有对所有资源的完全访问权限
      3. 默认情况下,应用程序不应获得对Kubernetes API的写访问权限
      4. 对于某些用途,可以有Kubernetes API写权限。
    • 四个要求转化为五个单独的角色:
      1. ReadOnly
      2. PowerUser
      3. Operator
      4. Controller
      5. Admin
    • 更多信息参考:access-control-roles-and-service-accounts

自定义策略

  • 只允许从已知registry部署容器
    • 可能要考虑的最常见的自定义策略之一是限制可以在群集中部署的镜像。
    • 参考文档说明了如何使用开放策略代理来限制未批准的镜像。
  • 强制Ingress主机名唯一
    • 用户创建Ingress清单时,可以使用其中的任何主机名。
    • 但是,可能希望阻止用户多次使用相同的主机名并互相覆盖。
    • Open Policy Agent的官方文档包含有关如何在validation Webhook中检查Ingress资源的教程。
  • 仅在Ingress主机名中使用批准的域名
    • 用户创建Ingress清单时,可以使用其中的任何主机名。
    • 但是,可能希望阻止用户使用无效的主机名。
    • Open Policy Agent的官方文档包含有关如何在validation Webhook中检查Ingress资源的教程。

Part3 集群配置

该部分还在进行中。如果对这部分内容有意见,欢迎提issue。

集群要求

  • 集群通过CIS基准测试

    • 互联网安全中心提供了一些准则和基准测试,以确保代码安全的最佳做法
    • 他们还维护了Kubernetes的基准,可以从官方网站上下载该基准。
    • 虽然可以阅读冗长的指南并手动检查集群是否符合要求,但更简单的方法是下载并执行kube-bench
    • kube-bench是一个工具,用于自动执行CIS Kubernetes基准测试并报告集群中的错误配置。

    请注意,无法使用kube-bench检查托管集群(例如GKE,EKS和AKS)的主节点。主节点由云提供商控制。

  • 禁用云提供商的元数据API

    • 云平台(AWS,Azure,GCE等)通常将本地元数据服务公开给实例。
    • 默认情况下,实例上运行的Pod可以访问这些API,并且可以包含该节点的云凭据或诸如kubelet凭据之类的置备数据。
    • 这些凭据可用于在群集内升级或升级到同一帐户下的其他云服务。
  • 限制对Alpha或Beta功能的访问

    • Alpha和Beta Kubernetes功能正在积极开发中,可能会存在限制或错误,从而导致安全漏洞。
    • 始终评估Alpha或Beta功能可能提供的价值,以防对安全状况造成潜在风险。
    • 如有疑问,请禁用不使用的功能。

身份认证

  • 使用OpenID(OIDC)令牌作为用户身份验证策略

    • Kubernetes支持各种身份验证方法,包括OpenID Connect(OIDC)。
    • OpenID Connect允许单点登录(SSO)(例如Google身份)连接到Kubernetes集群和其他开发工具。
    • 无需单独记住或管理凭据。
    • 可能有多个群集连接到同一OpenID提供程序。
    • 更多信息参考:kubernetes-single-sign-one-less-identity
  • ServiceAccount令牌仅适用于应用程序和控制器

    • ServiceAccount不应用于尝试与Kubernetes群集进行交互的最终用户,但对于在Kubernetes上运行的应用程序和工作负载,它们是首选的身份验证策略。

日志设置

  • 有一个日志保留和归档策略
    • 应该保留30-45天的历史日志。
  • 从节点,控制平面,审计中收集日志
    • 从哪些地方收集日志:
      1. 节点 (kubelet, container runtime)
      2. 控制平面 (API server, scheduler, controller manager)
      3. Kubernetes审计 (all requests to the API server)
    • 应该收集什么:
      1. 应用名称。从元数据标签中检索。
      2. 应用程序实例。从元数据标签中检索。
      3. 应用程序版本。从元数据标签中检索。
      4. 集群ID。从Kubernetes集群检索。
      5. 容器名称。从Kubernetes API检索。
      6. 运行此容器的群集节点。从Kubernetes集群检索。
      7. 运行容器的Pod名称。从Kubernetes集群检索。
      8. 命名空间。从Kubernetes集群检索。
  • 在每个节点上最好有一个守护程序来收集日志,而不是sidecar
    • 应用程序日志应输出到标准输出,而不是文件。
    • 每个节点上的守护程序可以从容器运行时收集日志(如果记录到文件,则可能需要每个pod的sidecar容器)。
  • 提供日志聚合工具
    • 使用日志聚合工具,例如EFK技术栈(Elasticsearch,Fluentd,Kibana),DataDog,Sumo Logic,Sysdig,GCP Stackdriver,Azure Monitor,AWS CloudWatch。