K3S:理解网络模型并应用 TLS 证书

K3S:理解网络模型并应用 TLS 证书

为了更多地了解 K8S,我开始运行单服务器/节点 K3S 集群作为家庭实验室。但我认为我对网络模型的理解陷入了僵局,也许是 K3S 特有的。

到目前为止一切顺利,除了我想将 TLS 证书应用于我已经设置的一些服务+入口。

在其中一个示例中,我已将 TLS 证书配置为机密,并将其应用于与服务关联的 Ingress,但我总是得到TRAEFIK DEFAULT CERT,您可以看到这里

据我了解,K3S 预装了 Traefik 和 ServiceLB,以便不依赖云服务的外部负载均衡器(AWS 等)。我的第一个猜测是 Traefik 会“发现”我在 Ingress 中设置的路由并代理 TLS 流量,从而使用我设置的证书。显然情况并非如此,所以我想我需要为 Traefik 实例本身设置一个 TLS 证书。

我的问题是

  • 如何在 K3S 中设置此证书?如果我计划让多个项目 / 域访问此集群,是否需要通配符证书?(我更喜欢按项目管理证书)
  • ServiceLB 和 Traefik 在 K3S 网络模型中扮演什么角色?如果 Traefik 获取 80 和 443 流量,它是否只是将流量转发到 ServiceLB,然后再将其转发到 Ingress 资源?

如果需要的话,这是我的入口/服务配置


resource kubernetes_service_v1 snitch_service {
  metadata {
    name = "snitch"
    namespace = module.namespace.name
  }


  spec {
    selector = {
      app = "snitch"
    }

    type = "LoadBalancer"

    port {
      name = "main"
      port = 3010
      target_port = 3000
      node_port = 30003
    }
  }
}



resource kubernetes_secret_v1 tls_secret {
  metadata {
    name = "snitch-tls-cert"
    namespace = "my-namespace"
  }

  type = "kubernetes.io/tls"

  data = {
    "tls.crt" = base64encode(my_certificate)
    "tls.key" = base64encode(my_certificate_private_key)
  }
}


resource kubernetes_ingress_v1 snitch_ingress {
  metadata {
    name = "snitch"
    namespace = "my-namespace"

    annotations = {
      "ingress.kubernetes.io/ssl-redirect" = "false"
    }
  }

  spec {
    tls {
      hosts = [local.subdomain]
      secret_name = "snitch-tls-cert"
    }

    rule {
      host = local.subdomain

      http {
        path {
          path = "/"
          path_type = "Prefix"

          backend {
            service {
              name = "snitch"
              port {
                number = 3010
              }
            }
          }
        }
      }
    }
  }
}

谢谢你!

答案1

入口服务通常是 TLS 终止发生的地方 - 也就是说,当您有一个像 Web 浏览器这样的客户端访问https://指向您的 kubernetes 集群的 URL 时,客户端将连接到入口服务,该服务协商安全连接,然后代理到您的后端服务的连接(许多入口服务器确实支持“直通”tls,其中终止实际上发生在后端,但由入口服务处理终止通常更容易且性能更高)。

kubernetes 文档

Ingress 将集群外部的 HTTP 和 HTTPS 路由公开给集群内的服务。流量路由由 Ingress 资源上定义的规则控制。

k3s 使用 traefik 作为入口服务,因此你需要在 traefik 中配置你的 ssl 证书。

配置默认证书

在没有特定于入口的 tls 证书的情况下,traefik 将使用默认证书来保护 tls 流量。开箱即用,traefik 将使用自签名证书。假设我的 k3s 集群在 hostname 上可用k3s.virt,我们可以像这样看到它:

$ openssl s_client -showcerts -connect k3s.virt:443 < /dev/null 2> /dev/null | openssl x509 -noout -subject -issuer
subject=CN = TRAEFIK DEFAULT CERT
issuer=CN = TRAEFIK DEFAULT CERT

我们可以按照本文替换默认证书。如果我们的大多数服务都将托管在同一个域中,那么使用通配符证书是有意义的。如果我创建一个新证书*.example.com并按照链接文章中的说明进行设置TLSStore,我们可以看到 Traefik 现在正在使用更新后的证书:

$ openssl s_client -showcerts -connect k3s.virt:443 < /dev/null 2> /dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName
subject=CN = default-router.example.com
issuer=CN = default-router.example.com
X509v3 Subject Alternative Name: 
    DNS:*.example.com

如果我部署一个 pod、服务和入口,如下所示:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: whoami
  name: whoami
spec:
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: whoami
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: whoami
  name: whoami
spec:
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
      - image: docker.io/traefik/whoami:latest
        name: whoami
        ports:
        - containerPort: 80
          name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app: whoami
  name: example
spec:
  ingressClassName: traefik
  rules:
  - host: whoami.example.com
    http:
      paths:
      - backend:
          service:
            name: whoami
            port:
              name: http
        path: /
        pathType: Prefix

然后(假设whoami.example.com解析到集群)我们将看到入口正在使用默认证书:

$ curl -sk -vvI https://whoami.example.com
...
* Server certificate:
*  subject: CN=default-router.example.com
*  start date: Mar 14 12:02:54 2024 GMT
*  expire date: Mar 12 12:02:54 2034 GMT
*  issuer: CN=default-router.example.com
...

配置每个 Ingress 证书

如果我们不想依赖默认证书(例如,如果我们需要使用不属于通配符所涵盖的域的主机名),我们可以为我们的服务配置一个唯一的证书。这在traefik ingress 文档

首先,我们需要创建一个证书并将其填充到 kubernetes secret 中。然后我们需要通过添加一个部分来更新我们的 Ingress 以引用该证书spec.tls

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  labels:
    app: whoami
  name: example
spec:
  ingressClassName: traefik
  rules:
  - host: whoami.example.com
    http:
      paths:
      - backend:
          service:
            name: whoami
            port:
              name: http
        path: /
        pathType: Prefix
  tls:
  - secretName: whoami-certificate

有了这个资源,我们看到入口不再使用默认证书:

$ curl -sk -vvI https://whoami.example.com
...
* Server certificate:
*  subject: CN=whoami.example.com
*  start date: Mar 14 12:07:16 2024 GMT
*  expire date: Mar 12 12:07:16 2034 GMT
*  issuer: CN=whoami.example.com
...

希望这能让你更清楚一些。我在这里提供的文档链接涵盖了更详细的内容。

我已将用于测试此配置的所有清单放在这个存储库


回复您的评论:

ServiceLB 负责监视服务type: LoadBalancer,并直接在集群节点上公开这些端口(例如这些文档)。例如,Traefik 作为kube-system命名空间中的一个服务,如下所示:

ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: web
  - name: websecure
    port: 443
    protocol: TCP
    targetPort: websecure
type: LoadBalancer

因此 ServiceLB 将集群节点上的端口 80 和 443 映射到此服务。

答案2

我已设法使 TLS 工作,部分归功于@larsks 的回答,它让我了解到 K8S 配置正在与 Traefik 实例一起玩弄,因此能够找到日志并查看问题。

我正在写一个答案,希望它将来能对其他人有所帮助。

本质上,当您添加/修改Ingress资源时,K8S 会告诉 Ingress 控制器“配置”自身。在我们的例子中,Traefik 实例根据定义Ingress(即添加路由)自行启动配置,如果定义了 TLS,它会尝试将机密中的证书添加到其 TLSStore。

由于 Traefik 实例正在应用新配置,因此可以在日志中找到大量必要信息。就我而言,存在不同的问题阻碍证书被添加到 TLSStore。这意味着 Traefik 只能终止“默认 TLS CERT”。我原本希望它使用我放入机密的任何内容,即使它们是随机字符串。

对于 K3S,你可以使用以下命令跟踪日志:

kubectl logs -f <the_traefik_pod_id> -n kube-system

创建自签名证书使我能够验证这一理论,因为现在我的 HTTPS 流量正由这个自签名证书而不是默认证书终止。@larsks 的回答证实了我的配置是合理的。如果它对任何人都有帮助,您可以使用以下命令创建自签名证书和新密钥:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

最后,多个教程谈到了将“base64 编码的证书”输入到机密中,这仅意味着以 PEM 格式输入它们,而不是对 PEM 本身进行 base64 编码(其中包含一个 b64 块)。 Traefik 日志也让我检测到了这个问题。您可以在我base64encode在机密中使用的原始问题中看到。如果您碰巧使用 Let's Encrypt,您可以连接 issuer_pem 和 certificate_pem 以创建链(在我的情况下,certificate_pem + issuer_pem按该顺序完成工作)。

相关内容