为了更多地了解 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,其中终止实际上发生在后端,但由入口服务处理终止通常更容易且性能更高)。
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
按该顺序完成工作)。