cert-manager是一个证书生命周期管理系统,支持证书的申请、部署等功能。您可以使用cert-manager颁发ASM网关的证书,从而可以使用HTTPS协议通过ASM网关访问服务,保证数据传输的安全。本文介绍如何使用cert-manager管理网关的证书。

背景信息

cert-manager支持签发自签名证书和DNS域名证书,从而可以使用HTTPS协议通过ASM网关访问服务。这两种证书的区别如下:
  • 自签名证书:自签名证书仅具有加密功能,无身份验证功能。您可以在命令行工具中使用HTTPS协议访问ASM网关,但是自签名证书不受Web浏览器的信任,Web浏览器检查HTTPS连接会标记为潜在风险并弹出错误消息,即无法在Web浏览器中使用HTTPS协议通过ASM网关访问服务。
  • DNS域名证书:DNS域名证书由受信任的CA机构颁发,兼具加密和身份验证功能。相比自签名证书,DNS域名证书具有更高的安全性,受到Web浏览器的信任。您可以同时在命令行工具和Web浏览器中使用HTTPS协议通过ASM网关访问服务。

在集群中安装cert-manager

  1. 在本地安装Helm。具体操作,请参见Helm
  2. 使用kubectl连接集群。具体操作,请参见获取集群KubeConfig并通过kubectl工具连接集群
  3. 执行以下命令,创建cert-manager命名空间。
    kubectl create namespace cert-manager
  4. 执行以下命令,添加cert-manager Chart。
    helm repo add jetstack https://charts.jetstack.io
  5. 执行以下命令,获取cert-manager Chart的最新信息。
    helm repo update
  6. 执行以下命令,安装cert-manager。
    说明 cert-manager的版本需要和Kubernetes版本保持兼容。关于cert-manager和Kubernetes版本的对应关系,请参见Supported Releases
    helm install \
      cert-manager jetstack/cert-manager \
      --namespace cert-manager \
      --version v1.1.0 \
      --set installCRDs=true

场景一:使用cert-manager签发自签名证书

步骤一:在集群中生成自签证书

  1. 使用以下内容,创建issuer.yaml
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: selfsigned
    spec:
      selfSigned: {}
    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: istio-ingressgateway-certs
    spec:
      isCA: true
      duration: 2160h # 90d
      secretName: istio-ingressgateway-certs
      commonName: istio-ingressgateway-certs
      subject:
        organizations:
        - cluster.local
        - cert-manager
      issuerRef:
        name: selfsigned
        kind: Issuer
        group: cert-manager.io
    ---
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
      name: istio-ingressgateway-certs
    spec:
      ca:
        secretName: istio-ingressgateway-certs
  2. 执行以下命令,创建自签名CA来颁发工作负载证书。
    kubectl apply -f issuer.yaml -n istio-system
  3. 执行以下命令,查看证书。
    kubectl get secret -n istio-system 

    预期输出:

    NAME                                        TYPE                             DATA         AGE
    istio-ingressgateway-certs                  kubernetes.io/tls                3            68m

步骤二:添加证书和私钥挂载卷到入口网关

  1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理
  2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择ASM网关 > 入口网关
  3. 入口网关页面的目标网关右侧,单击查看YAML
  4. 编辑对话框,添加如下信息到spec字段。
      secretVolumes:
        - mountPath: /etc/istio/ingressgateway-certs
          name: ingressgateway-certs
          secretName: istio-ingressgateway-certs

    添加之后的YAML文件示例如下:

    apiVersion: istio.alibabacloud.com/v1beta1
    kind: IstioGateway
    metadata:
      name: ingressgateway
      namespace: istio-system
    spec:
      clusterIds:
        - c58638b491a5248669b59609e0a17****
      cpu: {}
      externalTrafficPolicy: Local
      maxReplicas: 1
      minReplicas: 1
      ports:
        - name: status-port
          port: 15020
          targetPort: 15020
        - name: http2
          nodePort: 31380
          port: 80
          targetPort: 80
        - name: https
          nodePort: 31390
          port: 443
          targetPort: 443
        - name: tls
          port: 15443
          targetPort: 15443
      replicaCount: 1
      secretVolumes:
        - mountPath: /etc/istio/ingressgateway-certs
          name: ingressgateway-certs
          secretName: istio-ingressgateway-certs
      serviceAnnotations:
        service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
      serviceType: LoadBalancer

步骤三:在集群中检查入口网关配置

添加证书和私钥挂载卷到入口网关后,入口网关会加载证书与密钥。

  1. 执行以下命令,验证tls.crttls.key已被挂载到入口网关Pod中。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/ingressgateway-certs

    预期输出:

    lrwxrwxrwx 1 root root   14 May  9 01:14 tls.crt -> ..data/tls.crt
    lrwxrwxrwx 1 root root   14 May  9 01:14 tls.key -> ..data/tls.key
  2. 执行以下命令,检查Ingress gateway证书中的Subject字段的正确性。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}')  -- cat /etc/istio/ingressgateway-certs/tls.crt | openssl x509 -text -noout | grep 'Subject:'

    预期输出:

    Subject: O = cert-manager + O = cluster.local, CN = istio-ingressgateway-certs
  3. 执行以下命令,检查Ingress gateway的代理能够正确访问证书。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- curl  127.0.0.1:15000/certs

    预期输出:

    {
     "certificates": [
      {
       "ca_cert": [
        {
         "path": "\u003cinline\u003e",
         "serial_number": "4edcf3",
         "subject_alt_names": [],
         "days_until_expiration": "7251",
         "valid_from": "2022-01-06T06:17:00Z",
         "expiration_time": "2042-01-01T06:22:03Z"
        }
       ],
    ...........

步骤四:验证使用HTTPS协议访问服务是否成功

  1. 在集群中创建服务。
    1. 使用以下内容,创建myexample-nginx.conf
      本文示例服务基于Nginx实现,您需要为Nginx服务器创建配置文件,以域名aliyun.com的服务为例,以下内容定义请求根路径直接返回字样Welcome to aliyun.com!及状态码200
      events {
      }
      http {
        log_format main '$remote_addr - $remote_user [$time_local]  $status '
        '"$request" $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';
        access_log /var/log/nginx/access.log main;
        error_log  /var/log/nginx/error.log;
        server {
          listen 80;
          location / {
              return 200 'Welcome to aliyun.com!';
              add_header Content-Type text/plain;
          }
        }
      }
    2. 执行以下命令,创建Nginx服务器的配置项。
      kubectl create configmap myexample-nginx-configmap --from-file=nginx.conf=./myexample-nginx.conf
    3. 执行以下命令,为命名空间default启用Sidecar自动注入。
      kubectl label namespace default istio-injection=enabled
    4. 使用以下内容,创建myexampleapp.yaml
      apiVersion: v1
      kind: Service
      metadata:
        name: myexampleapp
        labels:
          app: myexampleapp
      spec:
        ports:
        - port: 80
          protocol: TCP
        selector:
          app: myexampleapp
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: myexampleapp
      spec:
        selector:
          matchLabels:
            app: myexampleapp
        replicas: 1
        template:
          metadata:
            labels:
              app: myexampleapp
          spec:
            containers:
            - name: nginx
              image: nginx
              ports:
              - containerPort: 80
              volumeMounts:
              - name: nginx-config
                mountPath: /etc/nginx
                readOnly: true
            volumes:
            - name: nginx-config
              configMap:
                name: myexample-nginx-configmap
    5. 执行以下命令,创建域名为aliyun.com的内部服务。
      kubectl apply -f myexampleapp.yaml
  2. 通过kubectl连接ASM。具体操作,请参见通过控制面kubectl访问Istio资源
  3. 在ASM中创建网关规则。
    1. 使用以下内容,创建istio-myexample-customingressgateway.yaml
      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: istio-myexample-customingressgateway
      spec:
        selector:
          istio: ingressgateway
        servers:
        - hosts:
          - '*.aliyun.com'
          port:
            name: http
            number: 80
            protocol: HTTP
          tls:
            httpsRedirect: true
        - hosts:
          - '*.aliyun.com'
          port:
            name: https
            number: 443
            protocol: HTTPS
          tls:
            mode: SIMPLE
            privateKey: /etc/istio/ingressgateway-certs/tls.key
            serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
    2. 执行以下命令,创建网关规则。
      kubectl apply -f istio-myexample-customingressgateway.yaml
  4. 在ASM中创建虚拟服务。
    1. 使用以下内容,创建istio-myexample-customvirtualservice.yaml
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: istio-myexample-customvirtualservice
      spec:
        hosts:
        - "www.aliyun.com"
        gateways:
        - istio-myexample-customingressgateway
        http:
        - route:
          - destination:
              host: myexampleapp.default.svc.cluster.local
              port:
                number: 80
    2. 执行以下命令,创建虚拟服务。
      kubectl apply -f istio-myexample-customvirtualservice.yaml
  5. 执行以下命令,在集群中获取入口网关服务的地址。
    kubectl get svc -n istio-system -l istio=ingressgateway
  6. 执行以下命令,通过HTTPS协议访问aliyun.com服务。
    curl -k -H Host:www.aliyun.com --resolve www.aliyun.com:443:{替换成真实的入口网关IP地址}  https://www.aliyun.com

    预期输出:

    Welcome to aliyun.com!

场景二:使用cert-manager签发DNS域名证书

准备工作

  • 已有阿里云域名,且该域名已备案。
  • 已在集群中配置Webhook,用于添加DNS记录。
    您可以使用alidns-webhook配置Webhook。本文通过在集群中执行以下命令,配置Webhook。
    https://gitee.com/godu/helminit/raw/master/cert-manager/alidns-cm-webhook.yaml

步骤一:为Webhook创建Secret

  1. 创建RAM用户。
    1. 使用阿里云账号登录RAM控制台
    2. 在访问控制控制台左侧导航栏,选择身份管理 > 用户
    3. 用户页面,单击创建用户
    4. 创建用户页面,输入登录名称显示名称,选中OpenAPI调用访问,然后单击确定
      记录RAM用户的AccessKey ID和AccessKey Secret。
  2. 授予RAM用户AliyunDNSFullAccess策略。
    1. 用户页面,单击上文创建的RAM用户右侧操作列下的添加权限
    2. 添加权限面板系统策略下,输入AliyunDNSFullAccess,单击AliyunDNSFullAccess,单击确定
  3. 授予RAM用户自定义策略。
    1. 在访问控制控制台左侧导航栏,选择权限管理 > 权限策略
    2. 权限策略页面,单击创建权限策略
    3. 创建权限策略页面,单击脚本编辑页签,然后输入以下内容,单击下一步
      {
          "Version": "1",
          "Statement": [
              {
                  "Action": "*",
                  "Resource": "acs:alidns:*:*:domain/#domain-name",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "alidns:DescribeSiteMonitorIspInfos",
                      "alidns:DescribeSiteMonitorIspCityInfos",
                      "alidns:DescribeSupportLines",
                      "alidns:DescribeDomains",
                      "alidns:DescribeDomainNs",
                      "alidns:DescribeDomainGroups"
                  ],
                  "Resource": "acs:alidns:*:*:*",
                  "Effect": "Allow"
              }
          ]
      }
                                      

      #domain-name需替换为您自己的域名,例如.example.com

    4. 输入权限策略的名称,单击确定
  4. Base64执行以下命令,为步骤1中的AccessKey ID和AccessKey Secret进行Base64编码。
    echo -n <AccessKey ID> | base64
    echo -n <AccessKey Secret>  | base64
  5. 在集群中创建Secret。
    1. 使用以下内容,创建alidns-secret.yaml
      apiVersion: v1
      kind: Secret
      metadata:
        name: alidns-secret
        namespace: cert-manager
      data:
        access-key: YOUR_ACCESS_KEY_BASE64  #base64编码后的AccessKey ID。
        secret-key: YOUR_SECRET_KEY_BASE64  #base64编码后的AccessKey Secret。
    2. 创建Secret。
      kubectl apply -f alidns-secret.yaml

步骤二:在集群中创建ClusterIssuer和Certificate

  1. 创建ClusterIssuer。
    1. 使用以下内容,创建letsencrypt-staging.yaml
      apiVersion: cert-manager.io/v1
      kind: ClusterIssuer
      metadata:
        name: letsencrypt-staging
      spec:
        acme:
          server: https://acme-staging-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            name: letsencrypt-staging-account-key
          solvers:
          - dns01:
              webhook:
                groupName: acme.yourcompany.com
                solverName: alidns
                config:
                  region: ""
                  accessKeySecretRef:
                    name: alidns-secret
                    key: access-key
                  secretKeySecretRef:
                    name: alidns-secret
                    key: secret-key
    2. 执行以下命令,创建ClusterIssuer。
      kubectl apply -f letsencrypt-staging.yaml
  2. 创建Certificate。
    1. 使用以下内容,创建example-tls.yaml
      apiVersion: cert-manager.io/v1
      kind: Certificate
      metadata:
        name: example-tls
        namespace: istio-system
      spec:
        # The secretName will store certificate content
        secretName: istio-ingressgateway-certs
        commonName: istio-ingressgateway-certs
        dnsNames:
        # Replace to your real DNS name
        - example.com
        - "*.example.com"
        issuerRef:
          name: letsencrypt-staging
          kind: ClusterIssuer
    2. 执行以下命令,创建Certificate。
      kubectl apply -f example-tls.yaml

      创建完成ClusterIssuer和Certificate后,cert-manager签发证书和私钥到istio-ingressgateway-certs Secret。

步骤三:添加证书和私钥挂载卷到入口网关

  1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理
  2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择ASM网关 > 入口网关
  3. 入口网关页面的目标网关右侧,单击查看YAML
  4. 编辑对话框,添加如下信息到spec字段。
      secretVolumes:
        - mountPath: /etc/istio/ingressgateway-certs
          name: ingressgateway-certs
          secretName: istio-ingressgateway-certs

    添加之后的YAML文件示例如下:

    apiVersion: istio.alibabacloud.com/v1beta1
    kind: IstioGateway
    metadata:
      name: ingressgateway
      namespace: istio-system
    spec:
      clusterIds:
        - c58638b491a5248669b59609e0a17****
      cpu: {}
      externalTrafficPolicy: Local
      maxReplicas: 1
      minReplicas: 1
      ports:
        - name: status-port
          port: 15020
          targetPort: 15020
        - name: http2
          nodePort: 31380
          port: 80
          targetPort: 80
        - name: https
          nodePort: 31390
          port: 443
          targetPort: 443
        - name: tls
          port: 15443
          targetPort: 15443
      replicaCount: 1
      secretVolumes:
        - mountPath: /etc/istio/ingressgateway-certs
          name: ingressgateway-certs
          secretName: istio-ingressgateway-certs
      serviceAnnotations:
        service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
      serviceType: LoadBalancer

步骤四:在集群中检查入口网关配置

  1. 执行以下命令,验证tls.crttls.key已被挂载到入口网关Pod中。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/ingressgateway-certs

    预期输出:

    lrwxrwxrwx 1 root root   14 May  9 01:14 tls.crt -> ..data/tls.crt
    lrwxrwxrwx 1 root root   14 May  9 01:14 tls.key -> ..data/tls.key
  2. 执行以下命令,检查Ingress gateway证书中的Subject字段的正确性。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}')  -- cat /etc/istio/ingressgateway-certs/tls.crt | openssl x509 -text -noout | grep 'Subject:'

    预期输出:

    Subject: O = cert-manager + O = cluster.local, CN = istio-ingressgateway-certs
  3. 执行以下命令,检查Ingress gateway的代理能够正确访问证书。
    kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- curl  127.0.0.1:15000/certs

    预期输出:

    {
     "certificates": [
      {
       "ca_cert": [
        {
         "path": "\u003cinline\u003e",
         "serial_number": "4edcf3",
         "subject_alt_names": [],
         "days_until_expiration": "7251",
         "valid_from": "2022-01-06T06:17:00Z",
         "expiration_time": "2042-01-01T06:22:03Z"
        }
       ],
    ...........

步骤五:验证使用HTTPS协议访问服务是否成功

  1. 在集群中创建服务。
    1. 使用以下内容,创建myexample-nginx.conf
      本文示例服务是基于Nginx实现的,您需要为Nginx服务器创建配置文件,以域名example.com的服务为例,以下内容定义请求根路径直接返回字样Welcome to aliyun.com!及状态码200。
      events {
      }
      http {
        log_format main '$remote_addr - $remote_user [$time_local]  $status '
        '"$request" $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';
        access_log /var/log/nginx/access.log main;
        error_log  /var/log/nginx/error.log;
        server {
          listen 80;
          location / {
              return 200 'Welcome to aliyun.com!';
              add_header Content-Type text/plain;
          }
        }
      }
    2. 执行以下命令,创建Kubernetes ConfigMap存储Nginx服务器的配置。
      kubectl create configmap myexample-nginx-configmap --from-file=nginx.conf=./myexample-nginx.conf
    3. 执行以下命令,为命名空间default启用Sidecar自动注入。
      kubectl label namespace default istio-injection=enabled
    4. 使用以下内容,创建myexampleapp.yaml
      apiVersion: v1
      kind: Service
      metadata:
        name: myexampleapp
        labels:
          app: myexampleapp
      spec:
        ports:
        - port: 80
          protocol: TCP
        selector:
          app: myexampleapp
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: myexampleapp
      spec:
        selector:
          matchLabels:
            app: myexampleapp
        replicas: 1
        template:
          metadata:
            labels:
              app: myexampleapp
          spec:
            containers:
            - name: nginx
              image: nginx
              ports:
              - containerPort: 80
              volumeMounts:
              - name: nginx-config
                mountPath: /etc/nginx
                readOnly: true
            volumes:
            - name: nginx-config
              configMap:
                name: myexample-nginx-configmap
    5. 执行以下命令,创建域名example.com的内部服务。
      kubectl apply -f myexampleapp.yaml
  2. 通过kubectl连接ASM。具体操作,请参见通过控制面kubectl访问Istio资源
  3. 在ASM中创建网关规则。
    1. 使用以下内容,创建istio-myexample-customingressgateway.yaml
      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: istio-myexample-customingressgateway
      spec:
        selector:
          istio: ingressgateway
        servers:
        - hosts:
          - '*.example.com'
          port:
            name: http
            number: 80
            protocol: HTTP
          tls:
            httpsRedirect: true
        - hosts:
          - '*.example.com'
          port:
            name: https
            number: 443
            protocol: HTTPS
          tls:
            mode: SIMPLE
            privateKey: /etc/istio/ingressgateway-certs/tls.key
            serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
    2. 执行以下命令,创建网关规则。
      kubectl apply -f istio-myexample-customingressgateway.yaml
  4. 在ASM中创建虚拟服务。
    1. 使用以下内容,创建istio-myexample-customvirtualservice.yaml
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: istio-myexample-customvirtualservice
      spec:
        hosts:
        - "www.example.com"
        gateways:
        - istio-myexample-customingressgateway
        http:
        - route:
          - destination:
              host: myexampleapp.default.svc.cluster.local
              port:
                number: 80
    2. 执行以下命令,创建虚拟服务。
      kubectl apply -f istio-myexample-customvirtualservice.yaml
  5. 在浏览器中输入域名,例如example.com
    返回以下页面,说明使用HTTPS协议访问服务成功。服务