使用 k3s 轻松制作 SSL 证书

如何在 Raspberry Pi 上使用 k3s 和 Letsencrypt 加密您的网站。
121 位读者喜欢这篇文章。
Files in a folder

上一篇文章中,我们在 k3s 集群上部署了几个简单的网站。这些都是未加密的站点。现在这很好,它们可以工作,但是未加密是非常上个世纪的了!如今,大多数网站都是加密的。在本文中,我们将安装 cert-manager 并使用它在我们的集群上部署 TLS 加密站点。这些站点不仅会被加密,而且它们将使用有效的公共证书,这些证书将从 Let's Encrypt 自动配置和自动续订!让我们开始吧!

所需材料

要跟随本文进行操作,您将需要我们在上一篇文章中构建的 k3s Raspberry Pi 集群。此外,您还需要一个公共静态 IP 地址和一个您拥有的域名,并且可以为其创建 DNS 记录。如果您有动态 DNS 提供商为您提供域名,那也可能有效。但是,在本文中,我们将使用静态 IP 和 CloudFlare 手动创建 DNS “A” 记录。

由于我们在本文中创建配置文件,如果您不想手动输入它们,可以在此处下载所有文件。

我们为什么要使用 cert-manager?

Traefik(k3s 预先捆绑)实际上内置了 Let's Encrypt 支持,因此您可能想知道为什么我们要安装第三方软件包来做同样的事情。 在撰写本文时,Traefik 的 Let's Encrypt 支持检索证书并将它们存储在文件中。 Cert-manager 检索证书并将它们存储在 Kubernetes secrets(密钥)中。 Secrets(密钥) 可以简单地按名称引用,因此,在我看来更容易使用。 这是我们在本文中使用 cert-manager 的主要原因。

安装 cert-manager

我们主要会遵循 cert-manager 文档 在 Kubernetes 上进行安装。 但是,由于我们正在使用 ARM 架构,因此我们将进行细微的更改,因此我们将在此处完成该过程。

第一步是创建 cert-manager 命名空间。 命名空间有助于将 cert-manager 的 pod 与我们的默认命名空间隔离,这样当我们使用我们自己的 pod 执行 kubectl get pods 之类的操作时,就不必看到它们。 创建命名空间很简单

kubectl create namespace cert-manager

安装说明让您下载 cert-manager YAML 配置文件,并一步将其应用于您的集群。 我们需要将其分成两步,以便为基于 ARM 的 Pi 修改文件。 我们将下载文件并在一步中完成转换

curl -sL \
https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml |\
sed -r 's/(image:.*):(v.*)$/\1-arm:\2/g' > cert-manager-arm.yaml

这将下载配置文件并更新所有包含的 docker 镜像以成为 ARM 版本。 要检查它做了什么

$ grep image: cert-manager-arm.yaml
          image: "quay.io/jetstack/cert-manager-cainjector-arm:v0.11.0"
          image: "quay.io/jetstack/cert-manager-controller-arm:v0.11.0"
          image: "quay.io/jetstack/cert-manager-webhook-arm:v0.11.0"

正如我们所见,这三个镜像现在都在镜像名称中添加了 -arm。 现在我们有了正确的文件,我们只需将其应用于我们的集群

kubectl apply -f cert-manager-arm.yaml

这将安装所有 cert-manager 组件。 我们可以通过使用 kubectl --namespace cert-manager get pods 进行检查,直到所有 pod 都处于 Running(运行中) 状态,从而知道安装何时完成。

实际上,cert-manager 的安装就是这样!

Let's Encrypt 的快速概述

Let's Encrypt 的好处是它们为我们免费提供公开验证的 TLS 证书! 这意味着我们可以拥有一个完全有效的 TLS 加密网站,任何人都可以访问我们的家庭或爱好事物,而无需自掏腰包支付 TLS 证书费用! 此外,当将 Let's Encrypt 证书与 cert-manager 一起使用时,获取证书的整个过程是自动化的。 证书续订也是自动化的!

但这如何工作? 这是该过程的简化说明。 我们(或 cert-manager 代表我们)向 Let's Encrypt 发出我们拥有的域名的证书请求。 Let's Encrypt 通过使用 ACME DNS 或 HTTP 验证机制来验证我们是否拥有该域名。 如果验证成功,Let's Encrypt 会向我们提供证书,cert-manager 会将这些证书安装在我们的网站(或其他 TLS 加密端点)中。 这些证书有效期为 90 天,之后需要重复该过程。 但是,Cert-manager 将自动为我们保持证书的最新状态。

在本文中,我们将使用 HTTP 验证方法,因为它更易于设置,并且适用于大多数用例。 这是幕后发生的基本过程。 Cert-manager 将向 Let's Encrypt 发出证书请求。 Let's Encrypt 将发出所有权验证质询作为回应。 质询是将 HTTP 资源放在正在请求证书的域名下的特定 URL 上。 理论是,如果我们可以将该资源放在该 URL 上,并且 Let's Encrypt 可以远程检索它,那么我们一定是域名的真正所有者。 否则,要么我们无法将资源放置在正确的位置,要么我们无法操纵 DNS 以允许 Let's Encrypt 访问它。 在这种情况下,cert-manager 将资源放在正确的位置,并自动创建一个临时的 Ingress(入口) 记录,该记录会将流量路由到正确的位置。 如果 Let's Encrypt 可以读取质询并且它是正确的,它将向 cert-manager 发回证书。 然后,Cert-manager 会将证书存储为 secrets(密钥),我们的网站(或任何其他东西)将使用这些证书来使用 TLS 保护我们的流量。

为质询准备我们的网络

我假设您希望在您的家庭网络上设置此功能,并且有一个路由器/接入点以某种方式连接到更广泛的互联网。 如果不是这种情况,则以下过程可能不是您所需要的。

为了使质询过程有效,我们需要我们正在请求证书的域名在端口 80 上路由到我们的 k3s 集群。 为此,我们需要告诉世界的 DNS 系统它在哪里。 因此,我们需要将域名映射到我们的公共 IP 地址。 如果您不知道您的公共 IP 地址是什么,您可以访问 WhatsMyIP 之类的地方,它会告诉您。 接下来,我们需要输入一个 DNS “A” 记录,将我们的域名映射到我们的公共 IP 地址。 为了使它可靠地工作,您需要一个静态公共 IP 地址,或者您可以使用动态 DNS 提供商。 一些动态 DNS 提供商会为您颁发一个域名,您可以使用这些说明。 我没有尝试过,所以我不能肯定它适用于所有提供商。

在本文中,我们将假设一个静态公共 IP 并使用 CloudFlare 设置 DNS “A” 记录。 如果您愿意,可以使用您自己的 DNS 提供商。 重要的是您能够设置 “A” 记录。

在本文的其余部分中,我将使用 k3s.carpie.net 作为示例域名,因为这是我拥有的域名。 您显然会将其替换为您拥有的任何域名。

好的,为了举例说明,假设我们的公共 IP 地址是 198.51.100.42。 我们将转到我们的 DNS 提供商的 DNS 记录部分,并添加类型为 “A” 的记录,名称为 k3s.carpie.net (CloudFlare 假定域名,因此在那里我们只需输入 k3s),并输入 198.51.100.42 作为 IPv4 地址。

请注意,有时 DNS 更新需要一段时间才能传播。 您可能需要几个小时才能解析名称。 名称解析是继续操作的必要条件。 否则,我们所有的证书请求都将失败。

我们可以使用 dig 命令检查名称是否已解析

$ dig +short k3s.carpie.net
198.51.100.42

继续运行上述命令,直到返回 IP。 关于 CloudFlare 的一个注意事项:CloudFlare 提供了一项通过代理流量来隐藏您的实际 IP 的服务。 在这种情况下,我们将获得 CloudFlare IP 而不是我们的 IP。 这对于我们的目的来说应该可以正常工作。

网络配置的最后一步是将我们的路由器配置为将端口 80 和 443 上的传入流量路由到我们的 k3s 集群。 可悲的是,路由器配置屏幕差异很大,所以我无法准确告诉您您的屏幕会是什么样子。 大多数时候,我们需要的管理页面位于“端口转发”或类似名称下。 我什至见过它列在“游戏”下(这显然是端口转发的主要用途)! 让我们看看我的路由器的配置是什么样的。

如果您有我的设置,您将转到 192.168.0.1 登录到路由器管理应用程序。 对于此路由器,它位于 NAT / QoS -> 端口转发 下。 在这里,我们将端口 80TCP 协议设置为转发到 192.168.0.50(我们的主节点 kmaster 的 IP)端口 80。 我们还将端口 443 设置为也映射到 kmaster。 这在技术上不是质询所必需的,但在本文的结尾,我们将部署一个启用 TLS 的网站,并且我们需要映射 443 才能访问它。 因此,现在继续映射它很方便。 我们保存并应用更改,我们应该一切就绪!

配置 cert-manager 以使用 Lets Encrypt(staging)

现在我们需要配置 cert-manager 以通过 Let's Encrypt 颁发证书。 Let's Encrypt 为我们提供了一个 staging(例如,测试)环境,以整理我们的配置。 它对错误和请求频率的容忍度更高。 如果我们在生产环境中笨手笨脚,我们很快就会发现自己被暂时禁止! 因此,我们将使用 staging 环境手动测试请求。

创建一个文件 letsencrypt-issuer-staging.yaml,内容如下

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: <your_email>@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: traefik

请务必将电子邮件地址更新为您的地址。 这是 Let's Encrypt 在出现问题或我们做坏事时联系我们的方式!

现在我们使用以下命令创建 issuer(颁发者)

kubectl apply -f letsencrypt-issuer-staging.yaml

我们可以使用以下命令检查 issuer(颁发者)是否已成功创建

kubectl get clusterissuers

Clusterissuers 是 cert-manager 创建的新 Kubernetes 资源类型。

现在让我们手动请求一个测试证书。 对于我们的站点,我们不需要这样做; 我们只是测试该过程以确保我们的配置正确。

创建一个证书请求文件 le-test-certificate.yaml,内容如下

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: k3s-carpie-net
  namespace: default
spec:
  secretName: k3s-carpie-net-tls
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: k3s.carpie.net
  dnsNames:
  - k3s.carpie.net

此记录只是说我们要使用名为 letsencrypt-stagingClusterIssuer(我们在上一步中创建的)请求域名 k3s.carpie.net 的证书,并将证书文件存储在名为 k3s-carpie-net-tls 的 Kubernetes secret(密钥)中。

像往常一样应用它

kubectl apply -f le-test-certificate.yaml

我们可以使用以下命令检查状态

kubectl get certificates

如果我们看到类似

NAME                    READY   SECRET                  AGE
k3s-carpie-net          True    k3s-carpie-net-tls      30s

我们就一切就绪了!(这里的关键是 READY(就绪)True(真))。

排除证书请求问题

那是顺利的情况。 如果 READY(就绪)False(假),我们可以给它一些时间,然后再次检查状态,以防需要一段时间。 如果它保持 False(假),那么我们遇到了需要排除故障的问题。 此时,我们可以遍历 Kubernetes 资源链,直到找到状态消息告诉我们问题所在。

假设我们执行了上面的请求,并且 READY(就绪)False(假)。 我们从以下命令开始故障排除

kubectl describe certificates k3s-carpie-net

这将返回大量信息。 通常,有用的信息位于 Events:(事件:) 部分,该部分通常位于底部。 假设最后一个事件是 Created new CertificateRequest resource "k3s-carpie-net-1256631848。 然后我们将描述该请求

kubectl describe certificaterequest k3s-carpie-net-1256631848

现在假设那里的最后一个事件是 Waiting on certificate issuance from order default/k3s-carpie-net-1256631848-2342473830

好的,我们可以描述 order(订单)

kubectl describe orders default/k3s-carpie-net-1256631848-2342473830

假设它有一个事件,上面写着 Created Challenge resource "k3s-carpie-net-1256631848-2342473830-1892150396" for domain "k3s.carpie.net"。 让我们描述 challenge(质询)

kubectl describe challenges k3s-carpie-net-1256631848-2342473830-1892150396

从这里返回的最后一个事件是 Presented challenge using http-01 challenge mechanism(使用 http-01 质询机制呈现质询)。 这看起来没问题,所以我们向上扫描描述输出,看到一条消息 Waiting for http-01 challenge propagation: failed to perform self check GET request … no such host(等待 http-01 质询传播:执行自检 GET 请求失败…没有这样的主机)。 终于! 我们找到了问题! 在这种情况下,no such host(没有这样的主机) 意味着 DNS 查找失败,因此我们应该返回并手动检查我们的 DNS 设置以及我们域名的 DNS 是否为我们正确解析,并进行任何必要的更改。

清理我们的测试证书

我们实际上想要我们使用的域名的真实证书,所以让我们继续清理我们刚刚创建的证书和 secret(密钥)

kubectl delete certificates k3s-carpie-net
kubectl delete secrets k3s-carpie-net-tls

配置 cert-manager 以使用 Let's Encrypt(生产)

现在我们已经让测试证书工作了,是时候升级到生产环境了。 就像我们为 Let's Encrypt staging 环境配置 cert-manager 一样,我们现在需要为生产环境做同样的事情。 创建一个文件(如果需要,您可以复制和修改 staging 环境的文件),命名为 letsencrypt-issuer-production.yaml,内容如下

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: <your_email>@example.com 
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: traefik

(如果您是从 staging 环境复制的,则唯一更改的是 server:(服务器:) URL。 不要忘记电子邮件)!

使用以下命令应用

kubectl apply -f letsencrypt-issuer-production.yaml

为我们的网站请求证书

重要的是要注意,我们到目前为止完成的所有步骤都是一次性设置! 对于将来的任何其他请求,我们可以从说明中的这一点开始!

让我们部署我们在上一篇文章中部署的同一个站点。 (如果您仍然有它,您可以只修改 YAML 文件。 如果没有,您可能需要重新创建并重新部署它)。

我们只需要修改 mysite .yamlIngress(入口) 部分为

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: mysite-nginx-ingress
  annotations:
    kubernetes.io/ingress.class: "traefik"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  rules:
  - host: k3s.carpie.net
    http:
      paths:
      - path: /
        backend:
          serviceName: mysite-nginx-service
          servicePort: 80
  tls:
  - hosts:
    - k3s.carpie.net
    secretName: k3s-carpie-net-tls

请注意,上面仅显示了 mysite.yamlIngress(入口) 部分。 更改是添加了注解 cert-manager.io/cluster-issuer: letsencrypt-prod。 这告诉 traefik 在创建证书时要使用哪个 issuer(颁发者)。 唯一添加的另一个是 tls:(tls:) 块。 这告诉 traefik 我们希望在主机 k3s.carpie.net 上启用 TLS,并且我们希望 TLS 证书文件存储在 secret(密钥) k3s-carpie-net-tls 中。

请记住,我们没有创建这些证书!(好吧,我们创建了类似命名的测试证书,但我们删除了那些证书。) Traefik 将读取此内容并查找 secret(密钥)。 当它找不到 secret(密钥)时,它会看到注解说我们要使用 letsencrypt-prod issuer(颁发者)来获取一个证书。 从那里,它将发出请求并将证书安装在 secret(密钥)中,为我们服务!

我们完成了! 让我们试一试。

它以其所有加密的 TLS 美丽形式呈现! 恭喜!

接下来阅读什么
User profile image.
Lee 首先是基督的追随者、丈夫和父亲,其次是软件工程师,并且内心深处是一个修补匠/创客。

8 条评论

我喜欢这篇文章,我很快就会在自己的网络上做类似的事情。 但是文本中有些错别字真的让我很在意。 想知道你们是否可以至少从开头段落更新以下内容
“There were non-encrypted sites.”
如果我理解正确,这应该读作如下
“These were non-encrypted sites.”

总是很乐意帮助改进内容。
Andrew Martin

感谢您的撰写.. 您在文章中提到

----
在本文中,我们将使用 HTTP 验证方法,因为它更易于设置,并且适用于大多数用例。 这是幕后发生的基本过程。 Cert-manager 将向 Let's Encrypt 发出证书请求。 Let's Encrypt 将发出所有权验证质询作为回应。 质询是将 HTTP 资源放在正在请求证书的域名下的特定 URL 上。 理论是,如果我们可以将该资源放在该 URL 上,并且 Let's Encrypt 可以远程检索它,那么我们一定是域名的真正所有者。
----

HTTP 资源到底在哪里? 我按照您的说明操作,一切都很好,但是我在我的 URL 上没有放置任何资源(据我所知)供 Let's Encrypt 验证。

嗨 Charles,

资源最终位于 `http:///.well-known/acme-challenge/`。 但是您不必将其放在那里,`cert-manager` 会在获取证书时为您处理创建此资源。 使用 `cert-manager` 进行自动化证书管理的优点之一!

回复 作者 Charles Chan (未验证)

非常感谢,您的文章帮助我完成了功能齐全的设置。

我已将我的 manifests(包括一个简单的 nginx 服务作为“网站”)放在这里: https://github.com/Chris927/k3s-letsencrypt-experiment

(FYI,Lee,您包含的 manifests 中的一些缩进是错误的,请比较我的,例如 “letsencrypt-issuer-production.yaml” 的 “spec” 部分)。

非常感谢这篇文章,帮助很大! :)

您知道如何告诉 traefik 将所有 http 流量重定向到 https 吗?

非常有用的教程 Lee。 我想为您的下一个教程提出请求。 您能否做一个关于如何在 k3s 上开发和部署一个简单的 python flask 应用程序的逐步教程。 谢谢!

© . All rights reserved.