应用开发的最终目标无疑是将应用暴露到互联网上。对于开发人员来说,Kubernetes 提供了 Ingress controller(Ingress 控制器)作为将请求路由到应用的机制,在一定程度上简化了这个过程。但并非一切都支持自助服务(尽管您可能希望如此):您仍然需要使用域名系统 (DNS) 中的记录,将应用的域名映射到 Ingress controller 的 IP 地址,并且仍然需要使用 TLS 证书来保护 HTTPS 连接。在大多数组织中,您自己并不拥有 DNS 或 TLS,必须与拥有 DNS 或 TLS 的一个或多个工作组进行协调。
对运维人员来说,事情未必变得更加简单。大多数组织很少需要更新 DNS 记录,因此对应的流程(包括业务规则和技术步骤)往往比较模糊或干脆没有。这意味着,当您需要添加 DNS 记录时,您首先需要查找文档,询问同事,或者(在最坏的情况下)自己想办法。您还需要确保遵守所有安全规则,并确保为防火墙正确标记 Ingress。
幸运的是,有一种方法可以让开发人员和运维人员的工作变得更轻松。在这篇文章中,我们演示了运维人员可如何配置 Kubernetes 部署,以便开发人员能够在 Kubernetes 环境中自助更新 DNS 记录和生成 TLS 证书。通过提前构建基础架构,您可以确保满足所有必要的业务要求和技术要求。
概述和先决条件
借助这款解决方案,如果开发人员需要将应用暴露到互联网上,只需按照提供的模板创建一个 Ingress controller 即可,提供的模板中包含一个位于 Kubernetes 安装所管理的域中的全限定域名 (FQDN)。Kubernetes 使用该模板为 Ingress controller 分配一个 IP 地址,创建 DNS A
记录将 FQDN 映射到 IP 地址,为 FQDN 生成 TLS 证书并将其添加到 Ingress controller。清理也同样简单:当 Ingress 被删除时,DNS 记录即被清理。
该解决方案利用了以下技术(安装和配置说明如下):
- Kubernetes.
- ExternalDNS.
- cert-manager.
- Let’s Encrypt.
- 来自 F5 NGINX 的 NGINX Ingress Controller,基于 NGINX 开源版或 NGINX Plus。该解决方案不能与由 Kubernetes 社区维护的 NGINX Ingress Controller 搭配使用。关于这两个项目之间差异的更多细节,请参见我们的博客。
在配置该解决方案之前,您需要:
- 带 egress (
LoadBalancer
) 对象的 Kubernetes 云安装。该解决方案使用 Linode,但其他云提供商也可以。 - 一个使用 Cloudflare 托管的域名,我们选择 Cloudflare 是因为它是 cert-manager 支持的 DNS 提供商之一,并且支持 ExternalDNS(截至本文撰写时,处于 beta 测试阶段)。我们强烈建议不要将该域名用于生产环境或任何其他关键目的。
- 访问 Cloudflare API(包括在免费套餐中)。
- Helm 用于安装和部署 Kubernetes。
kubectl
作为 Kubernetes 的命令行接口。- K9s(可选),一个结构良好的实体用户界面 (TUI),为与 Kubernetes 进行交互提供了一种更有条理的方式。
我们还假设您对 Kubernetes 有基本的了解(如何应用清单,使用 Helm 图表,以及使用 kubectl
命令来查看输出和排除故障)。了解 Let’s Encrypt 的基本概念会有所帮助,但不是必须的;要想了解概况,请查看我们的博客。您也不需要了解 cert-manager 的工作原理,但如果您对它(以及证书)如何与 NGINX Ingress Controller 协同工作感兴趣,请参阅我最近的帖子《在 Kubernetes 环境中实现证书管理自动化》。
我们已经在 MacOS 和 Linux 上测试了该解决方案。虽然我们还没有在 Windows Subsystem for Linux 版本 2 (WSL2) 上进行测试,但预计不会有任何问题。
注意: 该解决方案只是一个示例概念验证,不得用于生产环境。它并未包含所有运维和安全的最佳实践。关于这些主题的信息,请参见 cert-manager 和 ExternalDNS 文档。
部署解决方案
按照这些部分中的步骤来部署解决方案:
下载软件
- 下载您的 Cloudflare API 令牌
-
复制 NGINX Ingress Controller 仓库:
$ git clone https://github.com/nginxinc/kubernetes-ingress.git Cloning into 'kubernetes-ingress'... remote: Enumerating objects: 45176, done. remote: Counting objects: 100% (373/373), done. remote: Compressing objects: 100% (274/274), done. remote: Total 45176 (delta 173), reused 219 (delta 79), pack-reused 44803 Receiving objects: 100% (45176/45176), 60.45 MiB | 26.81 MiB/s, done. Resolving deltas: 100% (26592/26592), done.
-
验证您是否可以连接到 Kubernetes 集群。
$ kubectl cluster-info Kubernetes control plane is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443 KubeDNS is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
部署 NGINX Ingress Controller
-
使用 Helm,部署 NGINX Ingress Controller。请注意,我们正在添加三个非标准的配置选项:
controller.enableCustomResources
——指示 Helm 安装用于创建 NGINX VirtualServer 和 VirtualServerRoute 自定义资源的自定义资源定义 (CRD)。controller.enableCertManager
——将 NGINX Ingress Controller 配置为与 cert-manager 组件通信。controller.enableExternalDNS
——将 Ingress controller 配置为与 ExternalDNS 组件通信。
$ helm install nginx-kic nginx-stable/nginx-ingress --namespace nginx-ingress --set controller.enableCustomResources=true --create-namespace --set controller.enableCertManager=true --set controller.enableExternalDNS=true NAME: nginx-kic LAST DEPLOYED: Day Mon DD hh:mm:ss YYYY NAMESPACE: nginx-ingress STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The NGINX Ingress Controller has been installed.
-
验证 NGINX Ingress Controller 是否正在运行,并注意
EXTERNAL-IP
字段的值——NGINX Ingress Controller 的 IP 地址(此处为www.xxx.yyy.zzz
)。为方便阅读,输出结果分成了两行。$ kubectl get services --namespace nginx-ingress NAME TYPE CLUSTER-IP ... nginx-kic-nginx-ingress LoadBalancer 10.128.152.88 ... ... EXTERNAL-IP PORT(S) AGE ... www.xxx.yyy.zzz 80:32457/TCP,443:31971/TCP 3h8m
部署 cert-manager
在该解决方案中,cert-manager 在获取 TLS 证书时,使用 DNS-01 挑战类型,这需要在创建 ClusterIssuer 资源时提供 Cloudflare API 令牌。在该解决方案中,API 令牌作为 Kubernetes Secret 提供。
-
使用 Helm 部署 cert-manager:
$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.9.1 --set installCRDs=true NAME: cert-manager LAST DEPLOYED: Day Mon DD hh:mm:ss YYYY NAMESPACE: cert-manager STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: cert-manager v1.9.1 has been deployed successfully!
-
将 Cloudflare API 令牌部署为 Kubernetes Secret,并用它替换
<your-API-token>
:$ kubectl apply -f - <<EOF apiVersion: v1 kind: Secret metadata: name: Cloudflare-api-token-secret namespace: cert-manager type: Opaque stringData: api-token: "<your-API-token>" EOF secret/Cloudflare-api-token-secret created
-
创建一个 ClusterIssuer 对象,指定
Cloudflare-api-token-secret
(已在上一步中定义)作为检索令牌的位置。您也可以根据需要将metadata.name
字段中的example-issuer
(以及spec.acme.privateKeySecretRef.name
字段中的example-issuer-account-key
)替换为其他名称。$ kubectl apply -f - <<EOF apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: example-issuer namespace: cert-manager spec: acme: email: example@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: example-issuer-account-key solvers: - dns01: Cloudflare: apiTokenSecretRef: name: Cloudflare-api-token-secret key: api-token EOF clusterissuer.cert-manager.io/example-issuer created
-
验证 ClusterIssuer 是否已经部署完毕并准备就绪(
READY
字段的值为True
)。$ kubectl get clusterissuer NAME READY AGE example-issuer True 3h9m
部署 ExternalDNS
同 cert-manager 一样,ExternalDNS 项目需要使用 Cloudflare API 令牌来管理 DNS。 两个项目可以使用相同令牌,但并不是必须的。
-
为 NGINX Ingress Controller 创建 ExternalDNS CRD,以实现项目间的集成。
$ kubectl create -f ./kubernetes-ingress/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml customresourcedefinition.apiextensions.k8s.io/dnsendpoints.externaldns.nginx.org created
-
创建外部 DNS 服务 (
external-dns
)。由于该清单非常长,在此我们将其分成两部分。第一部分是配置账户、角色和权限。- 创建一个名为
external-dns
的 ServiceAccount 对象,以管理与 DNS 管理相关的所有写入和更新操作。 - 创建一个 ClusterRole 对象(也称
external-dns
)以定义所需的权限。 - 将 ClusterRole 绑定到 ServiceAccount。
$ kubectl apply -f - <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: external-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: ["externaldns.nginx.org"] resources: ["dnsendpoints"] verbs: ["get","watch","list"] - apiGroups: ["externaldns.nginx.org"] resources: ["dnsendpoints/status"] verbs: ["update"] - apiGroups: [""] resources: ["nodes"] verbs: ["list","watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: default EOF serviceaccount/external-dns created clusterrole.rbac.authorization.k8s.io/external-dns created clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
清单的第二部分创建 ExternalDNS 部署。
- 创建域过滤器,以限制 ExternalDNS 在管理域时可能造成的影响。 例如,您可以指定预发布环境的域名,以防止对生产环境的更改。 在这个示例中,我们把
domain-filter
设置为example.com
。 - 将
CF_API_TOKEN
环境变量设置为您的 Cloudflare API 令牌。对于<your-API-token>
,可以用实际的令牌或包含令牌的 Secret 进行替换。在后一种情况下,您还需要使用环境变量将 Secret 投射到容器中。 - 将
FREE_TIER
环境变量设置为"true"
(适合 Cloudflare 付费用户)。
$ kubectl apply -f - <<EOF --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.12.0 args: - --source=service - --source=ingress - --source=crd - --crd-source-apiversion=externaldns.nginx.org/v1 - --crd-source-kind=DNSEndpoint - --domain-filter=example.com - --provider=Cloudflare env: - name: CF_API_TOKEN value: "<your-API-token>" - name: FREE_TIER value: "true" EOF serviceaccount/external-dns created clusterrole.rbac.authorization.k8s.io/external-dns created clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created deployment.apps/external-dns created
- 创建一个名为
部署示例应用
使用名为 Cafe 的标准 NGINX Ingress Controller 示例应用进行测试。
-
部署 Cafe 应用。
$ kubectl apply -f ./kubernetes-ingress/examples/ingress-resources/complete-example/cafe.yaml deployment.apps/coffee created service/coffee-svc created deployment.apps/tea created service/tea-svc created
-
为 Cafe 应用部署 NGINX Ingress Controller。注意以下设置:
kind: VirtualServer
– 我们使用的是 NGINX VirtualServer 自定义资源,而不是标准的 Kubernetes Ingress 资源。spec.host
– 将cafe.example.com
替换为待部署主机的名称。该主机必须在 ExternalDNS 管理的域内。spec.tls.cert-manager.cluster-issuer
– 如果您一直用的是本文中指定的值,则为example-issuer
。如有必要,将其替换为您在“部署 cert-manager” 的第 3 步中选用的名称。spec.externalDNS.enable
– 值为true
时,ExternalDNS 将创建一条 DNSA
记录。
注意,这一步完成的时间在很大程度上取决于 DNS 提供商,因为 Kubernetes 正在与提供商的 DNS API 进行交互。
$ kubectl apply -f - <<EOF apiVersion: k8s.nginx.org/v1 kind: VirtualServer metadata: name: cafe spec: host: cafe.example.com tls: secret: cafe-secret cert-manager: cluster-issuer: example-issuer externalDNS: enable: true upstreams: - name: tea service: tea-svc port: 80 - name: coffee service: coffee-svc port: 80 routes: - path: /tea action: pass: tea - path: /coffee action: pass: coffee EOF virtualserver.k8s.nginx.org/cafe created
验证解决方案
-
验证 DNS
A
记录 – 即在ANSWER
SECTION
块中,FQDN(此处为cafe.example.com
)被映射到正确的 IP 地址 (www.xxx.yyy.zzz
)。$ dig cafe.example.com ; <<>> DiG 9.10.6 <<>> cafe.example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22633 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;cafe.example.com. IN A ;; ANSWER SECTION: cafe.example.com. 279 IN A www.xxx.yyy.zzz ;; Query time: 1 msec ;; SERVER: 2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359#53(2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359) ;; WHEN: Day Mon DD hh:mm:ss TZ YYYY ;; MSG SIZE rcvd: 67
-
检查证书是否有效(
READY
字段的值为True
)。$ kubectl get certificates NAME READY SECRET AGE cafe-secret True cafe-secret 8m51s
-
验证您能否访问应用。
$ curl https://cafe.example.com/coffee Server address: 10.2.2.4:8080 Server name: coffee-7c86d7d67c-lsfs6 Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset URI: /coffee Request ID: 91077575f19e6e735a91b9d06e9684cd $ curl https://cafe.example.com/tea Server address: 10.2.2.5:8080 Server name: tea-5c457db9-ztpns Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset URI: /tea Request ID: 2164c245a495d22c11e900aa0103b00f
当开发人员部署 NGINX Ingress controller 时后台会发生什么?
在该解决方案部署时,后台会进行很多操作。该图显示了当开发人员使用 NGINX VirtualServer 自定义资源部署 NGINX Ingress Controller 时后台进行的操作。请注意,一些操作细节已被省略。
- 开发人员使用 NGINX CRD 部署 VirtualServer 资源
- Kubernetes 使用 NGINX Ingress Controller 创建 VirtualServer
- NGINX Ingress Controller 调用 ExternalDNS 来创建 DNS
A
记录 - ExternalDNS 在 DNS 中创建
A
记录 - NGINX Ingress Controller 调用 cert-manager 来请求 TLS 证书
- cert-manager 添加 DNS 记录,以用于 DNS-01 挑战
- cert-manager 联系 Let’s Encrypt 以完成挑战
- Let’s Encrypt 根据 DNS 验证挑战
- Let’s Encrypt 颁发 TLS 证书
- cert-manager 向 NGINX Ingress Controller 提供 TLS 证书
- NGINX Ingress Controller 将受 TLS 保护的外部请求路由到应用 pod
故障排除
考虑到 Kubernetes 的复杂性以及我们所使用的组件,很难提供一套全面的故障排除指南。也就是说,只能提供一些基本建议来帮助您确定问题。
- 使用
kubectl
get
和kubectl
describe
命令来验证部署对象的配置。 - 使用
kubectl
logs
<component>
命令来查看各种已部署组件的日志文件。 - 使用 K9s 来检查安装;该软件用黄色或红色(根据严重程度)突出显示问题,并提供一个界面来访问日志和对象细节。
如果仍有问题,请通过添加“小N助手(微信号:nginxoss)”加入到我们的官方微信群,与我们进行直接交流。
如欲体验基于 NGINX Plus 的 NGINX Ingress Controller,请立即下载 30 天免费试用版,或者联系我们讨论您的用例。