一个 Projeted Volume 挂载 service account 引发的故障

原文链接:https://ieevee.com/tech/2023/12/13/projected-volume.html

最近接到一个业务报告的故障,提到 argo 创建的流水线运行失败,其中 Pod 去访问 kube API Server 时,偶发出现 401 认证不通过的问题。

API Server 报错如下:

E1212 18:37:53.063390       1 claims.go:126] unexpected validation error: *errors.errorString
E1212 18:37:53.063583       1 authentication.go:63] "Unable to authenticate the request" err="[invalid bearer token, Token could not be validated.]"

从报错上来看,的确没有通过 API Server 的认证。

func (v *validator) Validate(ctx context.Context, _ string, public *jwt.Claims, privateObj interface{}) (*apiserverserviceaccount.ServiceAccountInfo, error) {
 private, ok := privateObj.(*privateClaims)
 if !ok {
  klog.Errorf("jwt validator expected private claim of type *privateClaims but got: %T", privateObj)
  return nil, errors.New("Token could not be validated.")
 }
 nowTime := now()
 err := public.Validate(jwt.Expected{
  Time: nowTime,
 })
 switch {
 case err == nil:
 case err == jwt.ErrExpired:
  return nil, errors.New("Token has expired.")
 default:
  klog.Errorf("unexpected validation error: %T", err)
  return nil, errors.New("Token could not be validated.")
 }

先不看 API Server 的报错,看看业务使用的认证方式是什么。

通常业务使用的是 service account 来访问 API Server,这种情况业务代码使用 incluster kubeconfig 就可以通过 API Server 的认证了;如果需要相应的权限,则给这个 service account 授予对应的 RBAC 权限即可。

这种用法的一个弊端是一旦授予出去了,service account 的有效期就是永久的,回收很困难。

projected volumes 提供了一种新的 service account 注入的方法:https://kubernetes.io/docs/concepts/storage/projected-volumes/#serviceaccounttoken

如下是一个示例:

apiVersion: v1
kind: Pod
metadata:
  name: sa-token-test
spec:
  containers:
  - name: container-test
    image: busybox:1.28
    command: ["sleep", "3600"]
    volumeMounts:
    - name: token-vol
      mountPath: "/service-account"
      readOnly: true
  serviceAccountName: default
  volumes:
  - name: token-vol
    projected:
      sources:
      - serviceAccountToken:
          audience: api
          expirationSeconds: 3600
          path: token

kubelet 会为 service account 临时生成一个 token,并将 token 注入到/service-account,token 有效期为3600秒。你可以将 token 取出来,写到 kubectl 使用的 config 中,同样可以访问 API Server。

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: 「CA」
    server: https://kubernetes-apiserver.kube-system.svc.global.tcs.internal:6443
  name: global
contexts:
- context:
    cluster: global
    user: xxx
  name: xxx@global
current-context: xxx@global
kind: Config
preferences: {}
users:
- name: xxx
  user:
    token: 「token」

回到这个问题,既然怀疑这个 token 有问题,于是我们将 Pod 的启动命令改成 sleep infinity,等 Pod 启动后,将 token 拿出来放到 config 中,发现 token 没有任何问题,而且过期时间也足够。

陷入了沉思。

不过没关系,我们回过来看上面 API Server 的报错。我们使用的是 k8s 1.22 版本,只有 ErrExpired 会打印具体的报错,其他的错误只会傻傻的打印 *errors.errorString

func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
 if e.Issuer != "" && e.Issuer != c.Issuer {
  return ErrInvalidIssuer
 }

 if e.Subject != "" && e.Subject != c.Subject {
  return ErrInvalidSubject
 }

 if e.ID != "" && e.ID != c.ID {
  return ErrInvalidID
 }

 if len(e.Audience) != 0 {
  for _, v := range e.Audience {
   if !c.Audience.Contains(v) {
    return ErrInvalidAudience
   }
  }
 }

 if !e.Time.IsZero() && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
  return ErrNotValidYet
 }

 if !e.Time.IsZero() && e.Time.Add(-leeway).After(c.Expiry.Time()) {
  return ErrExpired
 }

 return nil
}

从上面的代码来看,返回的错误情况有很多,但是最大的嫌疑,就是 ErrNotValidYet。于是检查了各个节点的时间,发现果然时钟不同步,有一台服务器的时钟慢了2分钟。

那么是为什么呢?

原因很简单。证书签发后,如果是发给时钟慢的节点,会被 API Server 认为证书签发在未来的一个时间,所以还没生效,认证失败;为什么取出来 token 就好了呢?因为这个动作是人来做的,等把 token 取出来编辑好 kubeconfig,已经过去了2分钟,证书也就生效了。

btw,token 是一个 jwt,可以到 jwt.io 上检查,可视化做的非常好。

2b0d21059c969eda78eeef4a2b0e4194.png

加入 Sealos 开源社区

体验像个人电脑一样简单的云操作系统

🏠官网链接

https://sealos.run

🐙GitHub 地址

https://github.com/labring/sealos

📑访问 Sealos 文档

https://sealos.run/docs/Intro

🏘️逛逛论坛

https://forum.laf.run/

往期推荐

通过优化 Laf 运行时,我们把云开发的成本降了 10 倍

2023-12-13

133b1cb6f0abc82201deb839f5c90dae.jpeg

没错,数据库确实应该放入 K8s 里!

2023-12-07

5ce7862c03b210bf6e0710f8774b0330.jpeg

屠龙少年终成恶龙:回望我在谷歌的 18 年

2023-11-24

978a374650956909c36cb2cfb8970d37.jpeg

关于 Sealos

Sealos 是一款以 Kubernetes 为内核的云操作系统发行版。它以云原生的方式,抛弃了传统的云计算架构,转向以 Kubernetes 为云内核的新架构,使企业能够像使用个人电脑一样简单地使用云。

关注 Sealos 公众号与我们一同成长👇👇👇

d196651d80278391421d1ddcd27661f2.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值