http 协议是一个全明文传输的协议,不加任何处理的 http 报文将在因特网中裸奔,只要有人对监听你的报文,你所有隐私将被人一览无余。Let’s Encrypt 是一家免费、开放、自动化的证书颁发机构(CA),为公众的利益而运行。它是一项由 Internet Security Research Group(ISRG)提供的服务。本着想要创建一个更安全,更尊重隐私的 Web 环境的初衷,Let’s Encrypt 以尽可能对用户友好的方式免费提供为网站启用 HTTPS(SSL/TLS)所需的数字证书。

http_to_https

但是 Let’s Encrypt 颁发的证书有个特点,有效期只有三个月。这意味着我们需要频繁续期证书,倘若手动续期,无疑会增加运维成本。有什么方式可以为我们自动颁发证书吗?当然有!ACME 无疑就是我们的最佳选择。

有一款很好用的 ACME 客户端叫 Certbot,可以帮助自动颁发、续期证书。在 kubernetes 的生态中有没有什么好的工具能帮助我们非常方便地使用证书么?当然有,那就是本期主题——cert-manager

cert-manager 是本地 Kubernetes 证书管理控制器。它可以帮助从各种来源颁发证书,例如 Let’s Encrypt,HashiCorp Vault,Venafi,简单的签名密钥对或自签名。它将确保证书有效并且是最新的,并在到期前尝试在配置的时间续订证书。

原理比较简单,本文就不讲了,主要谈谈来安装和使用。

安装

YAML 安装

  1. 创建 namespace

    kubectl create namespace cert-manager
    
  2. 安装 crd 和 cert-manager

    kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml
    

    值得说明的是:在 kubernetes 1.15 以前的版本需要在执行kubectl apply时添加一个参数--validate=false,否则在创建 CustomResourceDefinition 资源时会收到与 x-kubernetes-preserve-unknown-fields 字段有关的验证错误。

helm 安装

依次执行下述命令:

# Install the CustomResourceDefinition resources separately
kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.11/deploy/manifests/00-crds.yaml

# Create the namespace for cert-manager
kubectl create namespace cert-manager

# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io

# Update your local Helm chart repository cache
helm repo update

# Install the cert-manager Helm chart
helm install \
  --name cert-manager \
  --namespace cert-manager \
  --version v0.11.0 \
  jetstack/cert-manager

确认安装

kubectl get pods --namespace cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-5c6866597-zw7kh               1/1     Running   0          2m
cert-manager-cainjector-577f6d9fd7-tr77l   1/1     Running   0          2m
cert-manager-webhook-787858fcdb-nlzsq      1/1     Running   0          2m

使用

创建 issuer

在建好 cert-manager 后,我们需要继续创建 issuer 才能颁发证书。有两种 issuer 可供我们选择:ClusterIssuer 和 Issuer,两都配置方式都是一样的,所不同的是作用域。ClusterIssuer,顾名思义是在集群中使用,作用域为所有命名空间,相对应的 Issuer 只作用于所在的命名空间。一般用 ClusterIssuer 比较方便。这里也通过 ClusterIssuer 举例。

证书的验证方式也有两种,一个是 http-01,另一个是 dns-01,由于 dns-01 需要域名解析服务商的支持,所以,在这里就没有使用了。有关这两种方式的详细说明参照 https://letsencrypt.org/zh-cn/docs/challenge-types/

  1. 创建一个名叫letsencrypt-staging.yaml的文件,内容是:

    apiVersion: cert-manager.io/v1alpha2
    kind: ClusterIssuer
    metadata:
        name: letsencrypt-staging
    spec:
        acme:
            # You must replace this email address with your own.
            # Let's Encrypt will use this to contact you about expiring
            # certificates, and issues related to your account.
            email: user@example.com
            server: https://acme-staging-v02.api.letsencrypt.org/directory
            privateKeySecretRef:
            # Secret resource used to store the account's private key.
                name: example-issuer-account-key
            # Add a single challenge solver, HTTP01 using nginx
            solvers:
            - http01:
                ingress:
                    class: nginx
    
  2. 通过kubectl安装:

    kubectl apply -f letsencrypt-staging.yaml
    
  3. 确保创建成功

    kubectl describe clusterissuer letsencrypt-staging
    ...
    Status:
    Acme:
        Uri:  https://acme-staging-v02.api.letsencrypt.org/acme/acct/7571319
    Conditions:
        Last Transition Time:  2019-01-30T14:52:03Z
        Message:               The ACME account was registered with the ACME server
        Reason:                ACMEAccountRegistered
        Status:                True
        Type:                  Ready
    

Let’s Encrypt 不支持通过 HTTP-01 的方式颁发通配符证书。要颁发通配符证书,必须使用DNS-01。

单独颁发

单独颁发证书,其实是一种手动的方式,一般不这么使用,但这却能加深我们对这整套流程的理解。颁发证书即创建并处理 Certificate 资源的过程。

  1. 有如下certificate-example.yaml文件:

    apiVersion: cert-manager.io/v1alpha2
    kind: Certificate
    metadata:
        name: acme-crt
    spec:
        secretName: acme-crt-secret
        dnsNames:
        - foo.example.com
        issuerRef:
            name: letsencrypt-stagging
            kind: ClusterIssuer
    
  2. 使用kubectl创建 Certificate:

    kubectl apply -f certificate-example.yaml
    

    注:这前提是域名得配置好了

  3. 静待 1-2 分钟:

    执行下述命令,静待READY值变成True

    kubectl get certificate acme-crt -w
    
  4. 错误排查:

    如果整个颁发过程出现问题导致证书颁发失败,可以利用kubectl describe查看对应的 order 进行排查。order是一个订单,记录了这笔交易(虽然我们并未花钱),这可以告诉我们失败的原因。

    kubcectl describe order acme-crt
    

指定 issuer 颁发

更多的时候我们不需要手动创建 Certificate,只需要在创建 Ingress 时作好对应的注释,cert-manager 就将自动为我们创建。对应的 yaml 文件大致是长这样的:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    acme.cert-manager.io/http01-edit-in-place: "true"
    cert-manager.io/cluster-issuer: letsencrypt-stagging
    kubernetes.io/ingress.class: nginx
  name: example
spec:
  rules:
  - host: foo.example.com
    http:
      paths:
      - backend:
          serviceName: example
          servicePort: 9000
        path: /
  tls:
  - hosts:
    - foo.example.com
    secretName: acme-crt-secret

这将自动创建一个与上一步骤中相同的 Certificate。

注:acme.cert-manager.io/http01-edit-in-place: "true"这明 http-01 验证将就在这个 Ingress 中进行而不会另行创建。

配置默认 issuer

在 cert-manager 的启动命令中加上这么两句:

--set ingressShim.defaultIssuerName=letsencrypt-stagging \
--set ingressShim.defaultIssuerKind=ClusterIssuer

这就为 cert-manager 设置了一个默认的 Issuer 了,设置成功后,我们就可以把 Ingress 资源中的cert-manager.io/cluster-issuer: letsencrypt-stagging改成kubernetes.io/tls-acme: "true"效果是完全一样的。