这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
容器
打包应用及其运行依赖环境的技术。
每个运行的容器都是可重复的;
包含依赖环境在内的标准,意味着无论您在哪里运行它,您都会得到相同的行为。
容器将应用程序从底层的主机设施中解耦。
这使得在不同的云或 OS 环境中部署更加容易。
容器镜像
容器镜像是一个随时可以运行的软件包,
包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。
根据设计,容器是不可变的:你不能更改已经运行的容器的代码。
如果有一个容器化的应用程序需要修改,则需要构建包含更改的新镜像,然后再基于新构建的镜像重新运行容器。
容器运行时
容器运行环境是负责运行容器的软件。
Kubernetes 支持多个容器运行环境: Docker、
containerd、CRI-O
以及任何实现 Kubernetes CRI (容器运行环境接口)。
接下来
1 - 镜像
容器镜像(Image)所承载的是封装了应用程序及其所有软件依赖的二进制数据。
容器镜像是可执行的软件包,可以单独运行;该软件包对所处的运行时环境具有
良定(Well Defined)的假定。
你通常会创建应用的容器镜像并将其推送到某仓库,然后在
Pod 中引用它。
本页概要介绍容器镜像的概念。
镜像名称
容器镜像通常会被赋予 pause
、example/mycontainer
或者 kube-apiserver
这类的名称。
镜像名称也可以包含所在仓库的主机名。例如:fictional.registry.example/imagename
。
还可以包含仓库的端口号,例如:fictional.registry.example:10443/imagename
。
如果你不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。
在镜像名称之后,你可以添加一个 标签(Tag) (就像在 docker
或 podman
中也在用的那样)。
使用标签能让你辨识同一镜像序列中的不同版本。
镜像标签可以包含小写字母、大写字符、数字、下划线(_
)、句点(.
)和连字符(-
)。
关于在镜像标签中何处可以使用分隔字符(_
、-
和 .
)还有一些额外的规则。
如果你不指定标签,Kubernetes 认为你想使用标签 latest
。
注意: 你要避免在生产环境中使用 latest
标签,因为这会使得跟踪所运行的镜像版本变得
非常困难,同时也很难回滚到之前运行良好的版本。
正确的做法恰恰相反,你应该指定一个有意义的标签,如 v1.42.0
。
更新镜像
当你最初创建一个 Deployment、
StatefulSet、Pod
或者其他包含 Pod 模板的对象时,如果没有显式设定的话,Pod 中所有容器的默认镜像
拉取策略是 IfNotPresent
。这一策略会使得
kubelet
在镜像已经存在的情况下直接略过拉取镜像的操作。
如果你希望强制总是拉取镜像,你可以执行以下操作之一:
- 设置容器的
imagePullPolicy
为 Always
。
- 省略
imagePullPolicy
,并使用 :latest
作为要使用的镜像的标签;
Kubernetes 会将策略设置为 Always
。
- 省略
imagePullPolicy
和要使用的镜像标签。
- 启用 AlwaysPullImages
准入控制器(Admission Controller)。
说明:
对象被 创建 时,容器的 imagePullPolicy
总是被设置为某值,如果镜像的标签
后来发生改变,镜像拉取策略也不会被改变。
例如,如果你创建了一个 Deployment 对象,其中的镜像标签不是 :latest
,
后来 Deployment 的镜像被改为 :latest
,则 imagePullPolicy
不会被改变为
Always
。你必须在对象被初始创建之后手动改变拉取策略。
如果 imagePullPolicy
未被定义为特定的值,也会被设置为 Always
。
带镜像索引的多架构镜像
除了提供二进制的镜像之外,容器仓库也可以提供
容器镜像索引。
镜像索引可以根据特定于体系结构版本的容器指向镜像的多个
镜像清单。
这背后的理念是让你可以为镜像命名(例如:pause
、example/mycontainer
、kube-apiserver
)
的同时,允许不同的系统基于它们所使用的机器体系结构取回正确的二进制镜像。
Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)
。
为了向前兼容,请在生成较老的镜像时也提供后缀。
这里的理念是为某镜像(如 pause
)生成针对所有平台都适用的清单时,
生成 pause-amd64
这类镜像,以便较老的配置文件或者将镜像后缀影编码到其中的
YAML 文件也能兼容。
使用私有仓库
从私有仓库读取镜像时可能需要密钥。
凭证可以用以下方式提供:
- 配置节点向私有仓库进行身份验证
- 所有 Pod 均可读取任何已配置的私有仓库
- 需要集群管理员配置节点
- 预拉镜像
- 所有 Pod 都可以使用节点上缓存的所有镜像
- 需要所有节点的 root 访问权限才能进行设置
- 在 Pod 中设置 ImagePullSecrets
- 特定于厂商的扩展或者本地扩展
- 如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点
向容器仓库认证的机制
下面将详细描述每一项。
配置 Node 对私有仓库认证
如果你在节点上运行的是 Docker,你可以配置 Docker
容器运行时来向私有容器仓库认证身份。
此方法适用于能够对节点进行配置的场合。
说明: Kubernetes 默认仅支持 Docker 配置中的 auths
和 HttpHeaders
部分,
不支持 Docker 凭据辅助程序(credHelpers
或 credsStore
)。
Docker 将私有仓库的密钥保存在 $HOME/.dockercfg
或 $HOME/.docker/config.json
文件中。如果你将相同的文件放在下面所列的搜索路径中,kubelet
会在拉取镜像时将其用作凭据
数据来源:
{--root-dir:-/var/lib/kubelet}/config.json
{kubelet 当前工作目录}/config.json
${HOME}/.docker/config.json
/.docker/config.json
{--root-dir:-/var/lib/kubelet}/.dockercfg
{kubelet 当前工作目录}/.dockercfg
${HOME}/.dockercfg
/.dockercfg
说明: 你可能不得不为 kubelet
进程显式地设置 HOME=/root
环境变量。
推荐采用如下步骤来配置节点以便访问私有仓库。以下示例中,在 PC 或笔记本电脑中操作:
-
针对你要使用的每组凭据,运行 docker login [服务器]
命令。这会更新
你本地环境中的 $HOME/.docker/config.json
文件。
-
在编辑器中打开查看 $HOME/.docker/config.json
文件,确保其中仅包含你要
使用的凭据信息。
-
获得节点列表;例如:
-
如果想要节点名称:nodes=$(kubectl get nodes -o jsonpath='{range.items[*].metadata}{.name} {end}')
-
如果想要节点 IP ,nodes=$(kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}')
-
将本地的 .docker/config.json
拷贝到所有节点,放入如上所列的目录之一:
- 例如,可以试一下:
for n in $nodes; do scp ~/.docker/config.json root@"$n":/var/lib/kubelet/config.json; done
说明: 对于产品环境的集群,可以使用配置管理工具来将这些设置应用到
你所期望的节点上。
创建使用私有镜像的 Pod 来验证。例如:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: private-image-test-1
spec:
containers:
- name: uses-private-image
image: $PRIVATE_IMAGE_NAME
imagePullPolicy: Always
command: [ "echo", "SUCCESS" ]
EOF
输出类似于:
pod/private-image-test-1 created
如果一切顺利,那么一段时间后你可以执行:
kubectl logs private-image-test-1
然后可以看到命令的输出:
SUCCESS
如果你怀疑命令失败了,你可以运行:
kubectl describe pods/private-image-test-1 | grep 'Failed'
如果命令确实失败,输出类似于:
Fri, 26 Jun 2015 15:36:13 -0700 Fri, 26 Jun 2015 15:39:13 -0700 19 {kubelet node-i2hq} spec.containers{uses-private-image} failed Failed to pull image "user/privaterepo:v1": Error: image user/privaterepo:v1 not found
你必须确保集群中所有节点的 .docker/config.json
文件内容相同。
否则,Pod 会能在一些节点上正常运行而无法在另一些节点上启动。
例如,如果使用节点自动扩缩,那么每个实例模板都需要包含 .docker/config.json
,
或者挂载一个包含该文件的驱动器。
在 .docker/config.json
中配置了私有仓库密钥后,所有 Pod 都将能读取私有仓库中的镜像。
提前拉取镜像
说明: 该方法适用于你能够控制节点配置的场合。
如果你的云供应商负责管理节点并自动置换节点,这一方案无法可靠地工作。
默认情况下,kubelet
会尝试从指定的仓库拉取每个镜像。
但是,如果容器属性 imagePullPolicy
设置为 IfNotPresent
或者 Never
,
则会优先使用(对应 IfNotPresent
)或者一定使用(对应 Never
)本地镜像。
如果你希望使用提前拉取镜像的方法代替仓库认证,就必须保证集群中所有节点提前拉取的镜像是相同的。
这一方案可以用来提前载入指定的镜像以提高速度,或者作为向私有仓库执行身份认证的一种替代方案。
所有的 Pod 都可以使用节点上提前拉取的镜像。
在 Pod 上指定 ImagePullSecrets
说明: 运行使用私有仓库中镜像的容器时,建议使用这种方法。
Kubernetes 支持在 Pod 中设置容器镜像仓库的密钥。
使用 Docker Config 创建 Secret
运行以下命令,将大写字母代替为合适的值:
kubectl create secret docker-registry <名称> \
--docker-server=DOCKER_REGISTRY_SERVER \
--docker-username=DOCKER_USER \
--docker-password=DOCKER_PASSWORD \
--docker-email=DOCKER_EMAIL
如果你已经有 Docker 凭据文件,则可以将凭据文件导入为 Kubernetes
Secret,
而不是执行上面的命令。
基于已有的 Docker 凭据创建 Secret
解释了如何完成这一操作。
如果你在使用多个私有容器仓库,这种技术将特别有用。
原因是 kubectl create secret docker-registry
创建的是仅适用于某个私有仓库的 Secret。
说明: Pod 只能引用位于自身所在名字空间中的 Secret,因此需要针对每个名字空间
重复执行上述过程。
在 Pod 中引用 ImagePullSecrets
现在,在创建 Pod 时,可以在 Pod 定义中增加 imagePullSecrets
部分来引用该 Secret。
例如:
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
你需要对使用私有仓库的每个 Pod 执行以上操作。
不过,设置该字段的过程也可以通过为
服务账号
资源设置 imagePullSecrets
来自动完成。
有关详细指令可参见
将 ImagePullSecrets 添加到服务账号。
你也可以将此方法与节点级别的 .docker/config.json
配置结合使用。
来自不同来源的凭据会被合并。
使用案例
配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。
- 集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。
- 使用 Docker hub 上的公开镜像
- 无需配置
- 某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间
- 集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见
- 使用托管的私有 Docker 仓库。
- 可以托管在 Docker Hub 或者其他地方
- 按照上面的描述,在每个节点上手动配置
.docker/config.json
文件
- 或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限
- 使用控制镜像访问的托管容器镜像仓库服务
- 与手动配置节点相比,这种方案能更好地处理集群自动扩缩容
- 或者,在不方便更改节点配置的集群中,使用
imagePullSecrets
- 集群使用专有镜像,且有些镜像需要更严格的访问控制
- 集群是多租户的并且每个租户需要自己的私有仓库
- 确保 AlwaysPullImages 准入控制器。否则,所有租户的所有的 Pod 都可以使用所有镜像。
- 为私有仓库启用鉴权
- 为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secrert 发布到各租户的命名空间下。
- 租户将 Secret 添加到每个名字空间中的 imagePullSecrets
如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。
kubelet
将所有 imagePullSecrets
合并为一个虚拟的 .docker/config.json
文件。
接下来
2 - 容器环境
本页描述了在容器环境里容器可用的资源。
容器环境
Kubernetes 的容器环境给容器提供了几个重要的资源:
- 文件系统,其中包含一个镜像
和一个或多个的卷
- 容器自身的信息
- 集群中其他对象的信息
容器信息
容器的 hostname 是它所运行在的 pod 的名称。它可以通过 hostname
命令或者调用 libc 中的
gethostname
函数来获取。
Pod 名称和命名空间可以通过
下行 API
转换为环境变量。
Pod 定义中的用户所定义的环境变量也可在容器中使用,就像在 Docker 镜像中静态指定的任何环境变量一样。
集群信息
创建容器时正在运行的所有服务都可用作该容器的环境变量。
这里的服务仅限于新容器的 Pod 所在的名字空间中的服务,以及 Kubernetes 控制面的服务。
这些环境变量与 Docker 链接的语法相同。
对于名为 foo 的服务,当映射到名为 bar 的容器时,以下变量是被定义了的:
FOO_SERVICE_HOST=<the host the service is running on>
FOO_SERVICE_PORT=<the port the service is running on>
服务具有专用的 IP 地址。如果启用了
DNS插件,
可以在容器中通过 DNS 来访问服务。
接下来
3 - 容器运行时类(Runtime Class)
FEATURE STATE: Kubernetes v1.20 [stable]
本页面描述了 RuntimeClass 资源和运行时的选择机制。
RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用于运行 Pod 中的容器。
动机
你可以在不同的 Pod 设置不同的 RuntimeClass,以提供性能与安全性之间的平衡。
例如,如果你的部分工作负载需要高级别的信息安全保证,你可以决定在调度这些 Pod
时尽量使它们在使用硬件虚拟化的容器运行时中运行。
这样,你将从这些不同运行时所提供的额外隔离中获益,代价是一些额外的开销。
你还可以使用 RuntimeClass 运行具有相同容器运行时但具有不同设置的 Pod。
设置
- 在节点上配置 CRI 的实现(取决于所选用的运行时)
- 创建相应的 RuntimeClass 资源
1. 在节点上配置 CRI 实现
RuntimeClass 的配置依赖于 运行时接口(CRI)的实现。
根据你使用的 CRI 实现,查阅相关的文档(下方)来了解如何配置。
说明: RuntimeClass 假设集群中的节点配置是同构的(换言之,所有的节点在容器运行时方面的配置是相同的)。
如果需要支持异构节点,配置方法请参阅下面的
调度。
所有这些配置都具有相应的 handler
名,并被 RuntimeClass 引用。
handler 必须符合 DNS-1123 命名规范(字母、数字、或 -
)。
2. 创建相应的 RuntimeClass 资源
在上面步骤 1 中,每个配置都需要有一个用于标识配置的 handler
。
针对每个 handler 需要创建一个 RuntimeClass 对象。
RuntimeClass 资源当前只有两个重要的字段:RuntimeClass 名 (metadata.name
) 和 handler (handler
)。
对象定义如下所示:
apiVersion: node.k8s.io/v1 # RuntimeClass 定义于 node.k8s.io API 组
kind: RuntimeClass
metadata:
name: myclass # 用来引用 RuntimeClass 的名字
# RuntimeClass 是一个集群层面的资源
handler: myconfiguration # 对应的 CRI 配置的名称
说明: 建议将 RuntimeClass 写操作(create、update、patch 和 delete)限定于集群管理员使用。
通常这是默认配置。参阅
授权概述了解更多信息。
使用说明
一旦完成集群中 RuntimeClasses 的配置,使用起来非常方便。
在 Pod spec 中指定 runtimeClassName
即可。例如:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
runtimeClassName: myclass
# ...
这一设置会告诉 kubelet 使用所指的 RuntimeClass 来运行该 pod。
如果所指的 RuntimeClass 不存在或者 CRI 无法运行相应的 handler,
那么 pod 将会进入 Failed
终止阶段。
你可以查看相应的事件,
获取执行过程中的错误信息。
如果未指定 runtimeClassName
,则将使用默认的 RuntimeHandler,相当于禁用 RuntimeClass 功能特性。
CRI 配置
关于如何安装 CRI 运行时,请查阅
CRI 安装。
dockershim
为 dockershim 设置 RuntimeClass 时,必须将运行时处理程序设置为 docker
。
Dockershim 不支持自定义的可配置的运行时处理程序。
通过 containerd 的 /etc/containerd/config.toml
配置文件来配置运行时 handler。
handler 需要配置在 runtimes 块中:
[plugins.cri.containerd.runtimes.${HANDLER_NAME}]
更详细信息,请查阅 containerd 配置文档:
https://github.com/containerd/cri/blob/master/docs/config.md
通过 cri-o 的 /etc/crio/crio.conf
配置文件来配置运行时 handler。
handler 需要配置在
crio.runtime 表
下面:
[crio.runtime.runtimes.${HANDLER_NAME}]
runtime_path = "${PATH_TO_BINARY}"
更详细信息,请查阅 CRI-O 配置文档。
调度
FEATURE STATE: Kubernetes v1.16 [beta]
通过为 RuntimeClass 指定 scheduling
字段,
你可以通过设置约束,确保运行该 RuntimeClass 的 Pod 被调度到支持该 RuntimeClass 的节点上。
如果未设置 scheduling
,则假定所有节点均支持此 RuntimeClass 。
为了确保 pod 会被调度到支持指定运行时的 node 上,每个 node 需要设置一个通用的 label 用于被
runtimeclass.scheduling.nodeSelector
挑选。在 admission 阶段,RuntimeClass 的 nodeSelector 将会与
pod 的 nodeSelector 合并,取二者的交集。如果有冲突,pod 将会被拒绝。
如果 node 需要阻止某些需要特定 RuntimeClass 的 pod,可以在 tolerations
中指定。
与 nodeSelector
一样,tolerations 也在 admission 阶段与 pod 的 tolerations 合并,取二者的并集。
更多有关 node selector 和 tolerations 的配置信息,请查阅
将 Pod 分派到节点。
Pod 开销
FEATURE STATE: Kubernetes v1.18 [beta]
你可以指定与运行 Pod 相关的 开销 资源。声明开销即允许集群(包括调度器)在决策 Pod 和资源时将其考虑在内。
若要使用 Pod 开销特性,你必须确保 PodOverhead
特性门控
处于启用状态(默认为启用状态)。
Pod 开销通过 RuntimeClass 的 overhead
字段定义。
通过使用这些字段,你可以指定使用该 RuntimeClass 运行 Pod 时的开销并确保 Kubernetes 将这些开销计算在内。
接下来
4 - 容器生命周期回调
这个页面描述了 kubelet 管理的容器如何使用容器生命周期回调框架,
藉由其管理生命周期中的事件触发,运行指定代码。
概述
类似于许多具有生命周期回调组件的编程语言框架,例如 Angular、Kubernetes 为容器提供了生命周期回调。
回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时运行在处理程序中实现的代码。
容器回调
有两个回调暴露给容器:
PostStart
这个回调在容器被创建之后立即被执行。
但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。
没有参数传递给处理程序。
PreStop
在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等)
而被终止之前,此回调会被调用。
如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。
在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。
Pod 的终止宽限周期在 PreStop
回调被执行之前即开始计数,所以无论
回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。
没有参数会被传递给处理程序。
有关终止行为的更详细描述,请参见
终止 Pod。
回调处理程序的实现
容器可以通过实现和注册该回调的处理程序来访问该回调。
针对容器,有两种类型的回调处理程序可供实现:
- Exec - 在容器的 cgroups 和名称空间中执行特定的命令(例如
pre-stop.sh
)。
命令所消耗的资源计入容器的资源消耗。
- HTTP - 对容器上的特定端点执行 HTTP 请求。
回调处理程序执行
当调用容器生命周期管理回调时,Kubernetes 管理系统根据回调动作执行其处理程序,
httpGet
和 tcpSocket
在kubelet 进程执行,而 exec
则由容器内执行 。
回调处理程序调用在包含容器的 Pod 上下文中是同步的。
这意味着对于 PostStart
回调,容器入口点和回调异步触发。
但是,如果回调运行或挂起的时间太长,则容器无法达到 running
状态。
PreStop
回调并不会与停止容器的信号处理程序异步执行;回调必须在
可以发送信号之前完成执行。
如果 PreStop
回调在执行期间停滞不前,Pod 的阶段会变成 Terminating
并且一致处于该状态,直到其 terminationGracePeriodSeconds
耗尽为止,
这时 Pod 会被杀死。
这一宽限期是针对 PreStop
回调的执行时间及容器正常停止时间的总和而言的。
例如,如果 terminationGracePeriodSeconds
是 60,回调函数花了 55 秒钟
完成执行,而容器在收到信号之后花了 10 秒钟来正常结束,那么容器会在其
能够正常结束之前即被杀死,因为 terminationGracePeriodSeconds
的值
小于后面两件事情所花费的总时间(55+10)。
如果 PostStart
或 PreStop
回调失败,它会杀死容器。
用户应该使他们的回调处理程序尽可能的轻量级。
但也需要考虑长时间运行的命令也很有用的情况,比如在停止容器之前保存状态。
回调递送保证
回调的递送应该是 至少一次,这意味着对于任何给定的事件,
例如 PostStart
或 PreStop
,回调可以被调用多次。
如何正确处理被多次调用的情况,是回调实现所要考虑的问题。
通常情况下,只会进行单次递送。
例如,如果 HTTP 回调接收器宕机,无法接收流量,则不会尝试重新发送。
然而,偶尔也会发生重复递送的可能。
例如,如果 kubelet 在发送回调的过程中重新启动,回调可能会在 kubelet 恢复后重新发送。
调试回调处理程序
回调处理程序的日志不会在 Pod 事件中公开。
如果处理程序由于某种原因失败,它将播放一个事件。
对于 PostStart
,这是 FailedPostStartHook
事件,对于 PreStop
,这是 FailedPreStopHook
事件。
您可以通过运行 kubectl describe pod <pod_name>
命令来查看这些事件。
下面是运行这个命令的一些事件输出示例:
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {default-scheduler } Normal Scheduled Successfully assigned test-1730497541-cq1d2 to gke-test-cluster-default-pool-a07e5d30-siqd
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulling pulling image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Created Created container with docker id 5c6a256a2567; Security:[seccomp=unconfined]
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulled Successfully pulled image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Started Started container with docker id 5c6a256a2567
38s 38s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 5c6a256a2567: PostStart handler: Error executing in Docker Container: 1
37s 37s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 8df9fdfd7054: PostStart handler: Error executing in Docker Container: 1
38s 37s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "main" with RunContainerError: "PostStart handler: Error executing in Docker Container: 1"
1m 22s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Warning FailedPostStartHook
接下来