使用 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 的 Pis 修改文件。 我们将一步下载文件并进行转换

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 将发布所有权验证质询。 挑战是在请求证书的域名下的特定 URL 上放置一个 HTTP 资源。 该理论是,如果我们可以将该资源放置在该 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 的注意事项:ClouldFlare 提供一项通过代理流量来隐藏您实际 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 以使用 Let's Encrypt(测试环境)

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

创建一个文件,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 将通过此方式与我们联系!

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

kubectl apply -f letsencrypt-issuer-staging.yaml

我们可以通过以下方式检查颁发者是否已成功创建

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

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

排除证书请求问题

这是顺利的情况。如果 READYFalse,我们可以等待一段时间,然后再次检查状态,以防需要一些时间。如果它仍然是 False,那么我们需要排除一个问题。此时,我们可以遍历 Kubernetes 资源链,直到找到一条告诉我们问题的状态消息。

假设我们完成了上述请求,并且 READYFalse。我们开始使用以下命令进行故障排除

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

好的,我们可以描述订单

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

假设该事件说 Created Challenge resource "k3s-carpie-net-1256631848-2342473830-1892150396" for domain "k3s.carpie.net"。让我们描述一下挑战

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

从此处返回的最后一个事件是 Presented challenge using http-01 challenge mechanism。这看起来还不错,因此我们扫描描述输出并看到一条消息 Waiting for http-01 challenge propagation: failed to perform self check GET request … no such host。终于!我们找到了问题!在这种情况下,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 测试环境配置 cert-manager 一样,我们现在需要对生产环境执行相同的操作。创建一个文件(如果需要,可以复制并修改测试环境文件),命名为 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

(如果你从测试环境复制,则唯一更改的是 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 部分。更改是添加了 annotation cert-manager.io/cluster-issuer: letsencrypt-prod。这告诉 traefik 在创建证书时使用哪个颁发者。唯一添加的另一项是 tls: 块。这告诉 traefik 我们希望在主机 k3s.carpie.net 上使用 TLS,并且我们希望将 TLS 证书文件存储在 Secret k3s-carpie-net-tls 中。

请记住,我们没有创建这些证书!(好吧,我们创建了类似命名的测试证书,但是我们删除了它们。)Traefik 将读取此信息并开始查找 Secret。当它找不到它时,它会看到注释,说我们要使用 letsencrypt-prod 颁发者来获取证书。从那里,它将发出请求并将证书安装在 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 (未验证)

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

我已经把我的 Manifest 文件(包括一个普通的 nginx 服务作为“网站”)放在这里: https://github.com/Chris927/k3s-letsencrypt-experiment

(供你参考,Lee,你包含的 Manifest 文件中的一些缩进不正确,请比较我的文件,例如 "letsencrypt-issuer-production.yaml" 的 "spec" 部分)。

非常感谢这篇文章,帮了我大忙!:)

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

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

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.