This topic describes how to deploy a custom ingress gateway in Istio and how to use cert-manager to manage certificates.

Background information

Istio supports multiple custom ingress gateways to handle incoming connections at the edge of the mesh through different ports and uses different load balancers to isolate different traffic. cert-manager can be used to obtain certificates using an arbitrary signing key pair stored in a Kubernetes secret resource.

Prerequisites

  • You have deployed Istio in the Kubernetes cluster.

    Container Service for Kubernetes now supports quick deployment of Istio. You can Create an ACK cluster and Deploy Istio through the Container Service console.

  • You can connect to the Kubernetes cluster through CloudShell.
  • To make it easier to manage the cluster, we recommend that you include the gateway name in secretName. Specifically, secretName can be specified in either of the following formats: istio-{Gateway Name}-certs or istio-{Gateway Name}-ca-cert.

Enable cert-manager

  1. In the default Istio configuration, cert-manager is not enabled for certificate management. You can enable cert-manager after Istio is installed. Log on to the Container Service console.
  2. In the left-side navigation pane, choose Service Mesh > Istio Management to go to the management page.
  3. Click Update in the upper-right corner. In the dialog box that appears, modify the parameters. Set the value of certManager.enabled to true and click Update. Note: If you cannot find the certManager parameter, you can add it as the first line in YAML format.
    yaml

Generate a signing key pair

The CA Issuer does not automatically create or manage signing key pairs. You can use either of the following methods to obtain a signing key pair:
  • If you already have your own key pair, you need to name the private key and the certificate as ca.key and ca.crt respectively.
  • If you have no signing key pair, you can generate a key and certificate of type x509 by using the following steps.
    1. Run the following command to generate a CA private key:
      $ docker run -it -v $(pwd):/export frapsoft/openssl genrsa -out /export/ca.key 2048
    2. Run the following command to generate a self-signed certificate:
      $ docker run -it -v $(pwd):/export frapsoft/openssl req -x509 -new -nodes -key /export/ca.key -subj "/CN=myexample.com" -days 3650 -reqexts v3_req -extensions v3_ca -out /export/ca.crt

Save the signing key pair as a secret

Run the following commands to create a secret that contains a signing key pair in the default namespace:
kubectl create namespace myexample
kubectl create secret tls istio-myexample-customingressgateway-ca-certs \
   --cert=ca.crt \
   --key=ca.key \
   --namespace=myexample
Note
  • Create an Issuer that uses this key pair to generate signed certificates. To ensure that the Issuer can reference the key pair, store the key pair in a Kubernetes secret resource.
  • Issuers are scoped to namespaces and can only reference secrets in their own namespaces. Therefore, make sure that the key pair is in the same namespace as the Issuer.

Create an Issuer to reference the secret

Create a certmanager-secret-issuer.yaml file with the following content. Then run the kubectl apply -n myexample -f certmanager-secret-issuer.yaml command to create an Issuer that uses the key pair to generate signed certificates.
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  name: ca-issuer
  namespace: myexample
spec:
  ca:
    secretName: istio-myexample-customingressgateway-ca-certs

Obtain a signed certificate

You can now create the following Certificate resource, which specifies the required certificate. To use an Issuer to obtain a certificate, you must create the Certificate resource in the same namespace as the Issuer. Issuers are scoped into namespaces, as shown in this example. If you want to reuse signing key pairs across multiple namespaces, you can use a ClusterIssuer. First, run the following command to create a certificate for domain name myexample.com:

Create a myexample-certificate.yaml file with the following content. Then run the kubectl apply -n myexample -f myexample-certificate.yaml command to create a signed certificate.
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: myexample-certificate
  namespace: myexample
spec:
  secretName: istio-myexample-customingressgateway-certs
  issuerRef:
    name: ca-issuer
    # You can reference a ClusterIssuer by change the kind here. Default is the Issuer in the local namespace.
    kind: Issuer
  commonName: myexample.com
  organization:
  - MyExample CA
  dnsNames:
  - myexample.com
  - www.myexample.com

After the Certificate resource is created, cert-manager attempts to use Issuer ca-issuer to obtain a certificate. If the attempt is successful, the certificate is stored inside a secret resource named istio-myexample-customingressgateway-certs in the same namespace (myexample) as the Certificate resource.

Check the certificate and key

The preceding example sets the commonName field to myexample.com, which will be used as the common name of the certificate. The common name and all elements in the dnsNames list will be subject alternative names (SANs). If the common name is not set, the first element in the dnsNames list will be used as the common name, and all elements in the dnsNames list will also be SANs.

After the certificate is created, run the following command to check whether the certificate has been obtained.

Check the myexample-certificate certificate as follows:

kubectl describe certificate myexample-certificate
Name:         myexample-certificate
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"Certificate","metadata":{"annotations":{},"name":"myexample-certificate","namespace":"...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         Certificate
....
Spec:
  Common Name:  myexample.com
  Dns Names:
    myexample.com
    www.myexample.com
  Issuer Ref:
    Kind:  Issuer
    Name:  ca-issuer
  Organization:
    MyExample CA
  Secret Name:  istio-myexample-customingressgateway-certs
Status:
  Conditions:
    ....
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
....
Events:
  Type    Reason      Age   From          Message
  ----    ------      ----  ----          -------
  Normal  CertIssued  78s   cert-manager  Certificate issued successfully
			

If the last line shows Certificate issued successfully, it indicates that the certificate has been successfully created.

You can also run the kubectl get secret istio-myexample-customingressgateway-certs -o yaml command to check whether the issuance was successful. If successful, you can see a base64 encoded signed TLS key pair.

After the certificate has been obtained, cert-manager will keep checking its validity and attempt to renew the certificate when it is about to expire. For example, when the Not After field on the certificate is less than the current time plus 30 days, cert-manager considers the certificate to be close to expiry. For CA-based Issuers, cert-manager will issue certificates with the Not After field set to the current time plus 365 days.

Deploy a custom gateway

A gateway describes a load balancer that operates at the edge of the mesh receiving incoming or outgoing HTTP/TCP connections. Container Service for Kubernetes supports managing custom gateways through CustomResourceDefinition (CRD). The following YAML template defines a custom ingress gateway:

apiVersion: istio.alibabacloud.com/v1beta1
kind: IstioGateway
metadata:  
  labels:    
    controller-tools.k8s.io: "1.0"  
  name: "myexample-customingressgateway"  
spec:  
  maxReplicas: 5  
  minReplicas: 1  
  ports:  
  - name: status-port    
    port: 15020    
    targetPort: 15020  
  - name: http2    
    port: 80    
    targetPort: 80  
  - name: https    
    port: 443    
    targetPort: 0  
  - name: tls    
    port: 15443    
    targetPort: 15443  
  replicaCount: 1  
  serviceType: LoadBalancer  
  serviceAnnotations:    
    service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet  
Note Make sure that the gateway name is included in secretName. Specifically, secretName must be specified in either of the following formats: istio-{Gateway Name}-certs or istio-{Gateway Name}-ca-certs.

Create a myexample-customingressgateway.yaml file with the preceding contents. Then run the kubectl apply -n myexample -f myexample-customingressgateway.yaml command to create a signed certificate.

Define internal services

The internal services in this example are implemented based on NGINX. First, create a configuration file for the NGINX server. Take the internal services of domain myexample.com as an example. Define the requested root path to directly return sentence Welcome to myexample.com! This is one custom Istio Ingress Gateway powered by cert-manager! and the 200 status code.

The myexample-nginx.conf file contains the following contents:
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 myexample.com! This is one custom Istio Ingress Gateway powered by cert-manager!' ;
        add_header Content-Type text/plain;
    }
  }
}
  1. Run the following command to create a Kubernetes ConfigMap to store the configuration of the NGINX server.
    kubectl create configmap myexample-nginx-configmap --from-file=nginx.conf=./myexample-nginx.conf
  2. Run the following command to set the namespace to default and enable automatic sidecar injection:
    kubectl label namespace default istio-injection=enabled
    Note Make sure that labeling for automatic sidecar injection only occurs after IngressGateway has been created. This ensures that labels are not automatically injected into IngressGateway. You may also choose to not enable automatic injection and use manual injection instead. For more information, see Manual injection.
  3. Create a myexampleapp.yaml file with the following contents. Then run the kubectl apply -f myexampleapp.yaml command to create an internal service for domain name myexample.com.
    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
    
    EOF

Create a custom gateway configuration object

Create an istio-myexample-customingressgateway.yaml file with the following contents. Then run the kubectl apply -n myexample -f istio-myexample-customingressgateway.yaml command to create an Istio custom gateway configuration object.
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  annotations:
  name: istio-myexample-customingressgateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - '*.myexample.com'
    port:
      name: http
      number: 80
      protocol: HTTP
    tls:
      httpsRedirect: true
  - hosts:
    - '*.myexample.com'
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      privateKey: /etc/istio/myexample-customingressgateway-certs/tls.key
      serverCertificate: /etc/istio/myexample-customingressgateway-certs/tls.crt

Create a VirtualService

Create an istio-myexample-customvirtualservice.yaml file with the following contents. Then run the kubectl apply -n myexample -f istio-myexample-customvirtualservice.yaml command to create a VirtualService that connects to istio-myexample-customingressgateway.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istio-myexample-customvirtualservice
spec:
  hosts:
  - "www.myexample.com"
  gateways:
  - istio-myexample-customingressgateway
  http:
  - route:
    - destination:
        host: myexampleapp.default.svc.cluster.local
        port:
          number: 80
				

Access services using a gateway

  1. Take domain name myexample.com as an example. Run the kubectl get svc -n myexample -l istio=myexample-customingressgateway command to obtain the public IP address of the corresponding custom gateway service:
    NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
    istio-myexample-customingressgateway   LoadBalancer   172.19.XX.XX   106.14.XX.XX    ....   11m
    Note Replace the values of environment variables INGRESS_HOST and SECURE_INGRESS_PORT with the actual values, which can be found in parameter EXTERNAL-IP from step 1.
    INGRESS_HOST=106.14.XX.XX
    SECURE_INGRESS_PORT=443
  2. Run the following command to check whether the istio-ingressgateway pod has correctly loaded the certificate and private key:
    kubectl exec -it -n myexample $(kubectl -n myexample get pods -l istio=myexample-customingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/myexample-customingressgateway-certs

    Both tls.crt and tls.key are expected to be saved in this directory.

  3. Run the following command to check whether the Subject field in the ingress gateway certificate is correct.
    kubectl exec -i -n myexample $(kubectl get pod -l istio=myexample-customingressgateway  -n myexample -o jsonpath='{.items[0].metadata.name}')  -- cat /etc/istio/myexample-customingressgateway-certs/tls.crt | openssl x509 -text -noout | grep 'Subject:'
            Subject: O=MyExample CA, CN=myexample.com
  4. Run the following command to check whether the ingress gateway proxy is able to correctly access the certificate:
    kubectl exec -ti $(kubectl get po -l istio=myexample-customingressgateway -n myexample -o jsonpath={.items[0]..metadata.name}) -n myexample -- curl  127.0.0.1:15000/certs
    {
        "ca_cert": "",
        "cert_chain": "...."
    }

    You have now completed all the steps to deploy a custom ingress gateway using cert-manager.

Result

Run the following command to access the myexample.com service over HTTPS.
curl -k -H Host:www.myexample.com --resolve www.myexample.com:443:106.14.XX.XX  https://www.myexample.com
Welcome to myexample.com! This is one custom Istio Ingress Gateway powered by cert-manager!