IBM Cloud Kubernetes Service Custom Domain with TLS Certificate

5 min read

How do I configure a TLS certificate with a custom domain in a Kubernetes cluster so that I can access an application with the custom domain?

The short answer is defining a DNS alias for your custom domain, creating a secret with a TLS certificate of the custom domain and defining an Ingress resource that uses the custom domain to route incoming network traffic to the service of your application. 

In this article, I will demonstrate the detailed steps for how to configure a TLS certificate with a custom domain in a Kubernetes cluster so that you can clearly understand how to set it up on your IBM Cloud Kubernetes Service cluster.

Steps

  1. Register a custom domain.
  2. Define a DNS alias for your custom domain by specifying the IBM-provided domain.
  3. Create a secret in the namespace to use TLS termination where your application exists.
  4. Define an Ingress resource that uses your custom domain to route incoming network traffic to the service of your application.

Step 1: Register a custom domain

Register your custom domain with any Domain Name Service (DNS) provider or IBM Cloud Classic Infrastructure Domain Name Service. If you don't have a custom domain yet, you can register a domain here

I am going to use my custom domain tnexample.com in the IBM Cloud Domain Name Registration service:

I am going to use my custom domain tnexample.com in the IBM Cloud Domain Name Registration service:

Step 2. Define an alias for your custom domain

Define an alias for the custom domain by specifying the IBM-provided Ingress subdomain as a Canonical Name record (CNAME) in DNS:

  1. Verify the Ingress Subdomain host for your cluster:
    $ export YOURAPIKEY=********
    $ export REGION=au-syd
    $ export RESOURCEGROUP=default
    $ export CLUSTERID=cajdfrcs0idhc1uip6n0 
    $ ibmcloud login -r $REGION -g $RESOURCEGROUP -apikey $YOURAPIKEY
    $ ibmcloud ks cluster ls
    Name              ID                     State    Created      Workers   Location   Version                 Resource Group Name   Provider   
    mycluster-tn      cajdfrcs0idhc1uip6n0   normal   1 week ago   1         Sydney     1.22.10_1553            default               classic   
    $ ibmcloud ks cluster get --cluster $CLUSTERID  | grep Ingress
    Ingress Subdomain:              mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud
  2. From the dashboard in the IBM Cloud console, click the Menu icon and select Classic Infrastructure to get to the Classic Infrastructure landing page.
  3. In the Classic Infrastructure navigation, select Network > DNS > Forward Zones.
  4. Select the domain tnexample.com.
  5. In Add New Record section, select Resource Type: CNAME, type Host: *,  Resource Type: CNAME and Points To: mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud. Note: You need add . at the end of the Ingress Subdomain host:
    In Add New Record section, select Resource Type: CNAME, type Host: *,  Resource Type: CNAME and Points To: mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud. Note: You need add . at the end of the Ingress Subdomain host:
    In Add New Record section, select Resource Type: CNAME, type Host: *,  Resource Type: CNAME and Points To: mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud. Note: You need add . at the end of the Ingress Subdomain host:
  6. Confirm if the DNS works by using the commands nslookup and ping. You see the custom domain URL can be reached via the custom domain:
    % nslookup app.tnexample.com
    Server:                192.168.1.1
    Address:        192.168.1.1#53
    Non-authoritative answer:
    app.tnexample.com        canonical name = mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud.
    Name:        mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud
    Address: 168.1.52.126
    
    
    $ ping app.tnexample.com
    PING mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud (168.1.52.126): 56 data bytes
    64 bytes from 168.1.52.126: icmp_seq=0 ttl=54 time=114.156 ms
    64 bytes from 168.1.52.126: icmp_seq=1 ttl=54 time=23.914 ms
    64 bytes from 168.1.52.126: icmp_seq=2 ttl=54 time=24.528 ms
    ^C
    --- mycluster-tn-5be51ad3139a99d89cdf8f97c78ef71c-0000.au-syd.containers.appdomain.cloud ping statistics ---
    3 packets transmitted, 3 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 23.914/54.199/114.156/42.397 ms
    Alternatively, You can use IBM Cloud Internet Services (CIS), which offers capabilities to enhance your workflow — security, reliability and performance, including DNS management. See more details here.

Step 3. Create a secret in the namespace to use TLS termination

Create a secret in the namespace to use TLS termination where your apps exist that contains your own TLS certificate. 

Granting service access to classic infrastructure

If you manage domains by using the classic infrastructure DNS service, you must grant service access to its DNS service so that IBM Cloud Secrets Manager can validate the ownership of your domains. You'll need your classic infrastructure account credentials before you can grant access. To obtain your classic infrastructure username and API key, you can use the Access (IAM) section of the console.

  1. In the console, go to Manage > Access (IAM) > Users, then select the user's name.
  2. In the VPN password section, copy the Username value. In most cases, your classic infrastructure username is your <account_id>_<email_address> or IBM<number>:
    In the VPN password section, copy the Username value. In most cases, your classic infrastructure username is your <account_id>_<email_address> or IBM<number>:
  3. In the API keys section, create a classic infrastructure API key or find your existing key.
  4. Click the Actions icon > Details to copy the API key value:
    Click the Actions icon > Details to copy the API key value:
    Click the Actions icon > Details to copy the API key value:
  5. Assign your user permissions to manage DNS in the account. For more information about managing classic infrastructure access, see Classic infrastructure permissions:
    • Click the Classic infrastructure tab to manage your classic infrastructure permissions.
    • In the Services section, ensure that the Manage DNS permission is selected.

Adding DNS provider configurations to your Secrets Manager instance

  1. In the console, click the Menu icon > Resource List.
  2. From the list of services, select your instance of Secrets Manager (or create a Secret Manager instance).
  3. On the Secrets engines page, click the Public certificates tab.
  4. In the DNS providers table, click Add:
    In the DNS providers table, click Add:
  5. Enter name and Select the DNS provider: IBM Cloud classic infrastructure and click Next:
    Enter name and Select the DNS provider: IBM Cloud classic infrastructure and click Next:
  6. Enter the classic infrastructure username and API key and click Add:
    Enter the classic infrastructure username and API key and click Add:
  7. You should see the DNS provider configuration added:
    You should see the DNS provider configuration added:

Connecting third-party certificate authorities

Connect to a third-party certificate authority by adding a configuration to your instance with IBM Cloud Secrets Manager. A certificate authority (CA) is the entity that signs and issues your TLS certificates. By adding a CA configuration, you can specify the authority that you want to use when you order public certificates through Secrets Manager.

Creating a Let's Encrypt ACME account

Secrets Manager uses the Automatic Certificate Management Environment (ACME) protocol. The ACME protocol makes it possible to automatically obtain browser trusted certificates from a certificate authority without human intervention.

Create an account by using the ACME account creation tool. Once you create the account, keep the Account information (json file) and the Private key (pem file) in a safe place:

$ chmod +x acme-account-creation-tool
$ ./acme-account-creation-tool -e tnakajo@au1.ibm.com -o my-letsencrypt -d letsencrypt-prod
INFO[2022-04-18T14:07:19+10:00] Registering a new account with the CA        
INFO[2022-04-18T14:07:20+10:00] Account information written to file : my-letsencrypt-account-info.json 
INFO[2022-04-18T14:07:20+10:00] Private key written to file : my-letsencrypt-private-key.pem 

Adding a certificate authority configuration

Add certificate authority configurations to your service instance by using the Secrets Manager UI:

  1. In the console, click the Menu icon > Resource List.
  2. From the list of services, select your instance of Secrets Manager.
  3. On the Secrets engines page, click the Public certificates tab.
  4. In the Certificate authorities table, click Add.
  5. Enter name and select Let's Encrypt for the Certificate authority:
    Enter name and select Let's Encrypt for the Certificate authority:
  6. Add the private key file in PEM format that's associated with your ACME account:
    Add the private key file in PEM format that's associated with your ACME account:
  7. Click Add. You should see the certificate authority configured.

Ordering public certificates from third parties

You can also order a certificate by using Secrets Manager. When you order a certificate, domain validation takes place to verify the ownership of your selected domains. This process can take a few minutes to complete.

  1. In the console, click the Menu icon > Resource List.
  2. From the list of services, select your instance of Secrets Manager.
  3. In the Secrets table, click Add:
    In the Secrets table, click Add:
  4. From the list of secret types, click the TLS certificates tile:
    From the list of secret types, click the TLS certificates tile:
  5. Click the Order a public certificate tile:
    Click the Order a public certificate tile:
  6. Enter the details of your certificate:
    • Add a name and description to easily identify your certificate.
    • Select a certificate authority configuration: LetEncrypt.
    • Select a DNS provider configuration: ClassicInfraDNS.
    • Add the domains to include in your request: *.tnexample.com.
    • Click Order:
      Click Order:
  7. After you submit your certificate details, Secrets Manager sends your request to the selected certificate authority.
  8. After a certificate is issued, you can check the issuance details of your certificate by clicking the Actions icon > View details:
    After a certificate is issued, you can check the issuance details of your certificate by clicking the Actions icon > View details:
  9. Copy the certificate CRN value:
    Copy the certificate CRN value:

Creating a secret in the cluster

  1. Make sure the Secrets Manager instance is registered as an Ingress instance in the cluster:
    $ ibmcloud ks ingress instance ls --cluster $CLUSTERID
    Name                                Type              Is Default   Status    CRN   
    secretsmanager-tn                   secrets-manager   true         created   crn:v1:bluemix:public:secrets-manager:us-south:a/bde92b81610817c89a6b7b3f8e8813f8:64353325-db92-4433-86dd-a2855bb634fb::
  2. Create a secret with the certificate. I have a sample hello-world service running in the cluster. Refer to this blog post to use this sample:
    $ ibmcloud ks cluster config --cluster $CLUSTERID
    $ export NAMESPACE=project-a
    $ export DEPLOYMENT=hello-world-deployment
    $ export APPIMAGE=us.icr.io/tn_namespace/hello-world:1
    $ export SERVICENAME=hello-world-svc
    $ export SERVICEPORT=8080
    $ kubectl create deployment $DEPLOYMENT --image=$APPIMAGE --namespace $NAMESPACE
    deployment.apps/hello-world-deployment created
    $ kubectl expose deploy $DEPLOYMENT --name $SERVICENAME--port $SERVICEPORT --namespace $NAMESPACE
    service/hello-world-svc exposed
    $ kubectl get service --namespace $NAMESPACE
    NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
    hello-world-svc            ClusterIP   172.21.129.92    <none>        8080/TCP         13s
  3. Set CERTCRN as I copied the certificate CRN value in the previous step:
    $ export CERTCRN=crn:v1:bluemix:public:secrets-manager:us-south:a/bde92b81610817c89a6b7b3f8e8813f8:64353325-db92-4433-86dd-a2855bb634fb:secret:9a935e61-1a5c-8888-4d27-3fbd7dbe2e48
    $ export SECRETNAME=custom-cert-tnexample
  4. Run the command to create a secret:
    $ ibmcloud ks ingress secret create --cluster $CLUSTERID --name $SECRETNAME --cert-crn $CERTCRN --namespace $NAMESPACE --type TLS
    OK
  5. Verify the secret:
    $ ibmcloud ks ingress secret ls --cluster $CLUSTERID
    Name                                                 Namespace        CRN                                                                                                                                                                  Expires On                 Domain                                                                                 Status    Type   
    custom-cert-tnexample                                project-a        crn:v1:bluemix:public:secrets-manager:us-south:a/bde92b81610817c89a6b7b3f8e8813f8:64353325-db92-4433-86dd-a2855bb634fb:secret:9a935e61-1a5c-8888-4d27-3fbd7dbe2e48   2022-09-13T05:29:02+0000   *.tnexample.com                                                                        created   TLS   
    $ kubectl get secret -n $NAMESPACE
    NAME                    TYPE                                  DATA   AGE
    custom-cert-tnexample   kubernetes.io/tls                     2      27s

4. Define an Ingress resource that uses your custom domain to route incoming network traffic to the service

Create an Ingress resource to define the routing rules that use your custom domain to route traffic to your application.

  1. Create an Ingress with the custom domain host and the custom domain secret. The following is an example configuration for the Ingress resource (hello-world-ingress.yaml):
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: hello-world-ingress
      annotations:
        kubernetes.io/ingress.class: "public-iks-k8s-nginx"
    spec:
      tls:
      - hosts:
        - app.example.com
        secretName: custom-cert-tnexample
      rules:
      - host: app.tnexample.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-world-svc
                port:
                  number: 8080
  2. Apply the ingress configuration:
    $ kubectl apply -f hello-world-ingress.yaml
    ingress.networking.k8s.io/hello-world-ingress created
  3. Verify by accessing your app with the Ingress subdomain and the path https://CUSTOM-DOMAIN-HOST/APP-PATH, and you should see the HTTPS connection is established with the valid custom domain certificate:
    % curl https://app.tnexample.com -v
    *   Trying 168.1.52.126:443...
    * Connected to app.tnexample.com (168.1.52.126) port 443 (#0)
    * ALPN, offering h2
    * ALPN, offering http/1.1
    * successfully set certificate verify locations:
    *  CAfile: /etc/ssl/cert.pem
    *  CApath: none
    * (304) (OUT), TLS handshake, Client hello (1):
    * (304) (IN), TLS handshake, Server hello (2):
    * (304) (IN), TLS handshake, Unknown (8):
    * (304) (IN), TLS handshake, Certificate (11):
    * (304) (IN), TLS handshake, CERT verify (15):
    * (304) (IN), TLS handshake, Finished (20):
    * (304) (OUT), TLS handshake, Finished (20):
    * SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
    * ALPN, server accepted to use h2
    * Server certificate:
    *  subject: CN=*.tnexample.com
    *  start date: Jun 15 05:29:03 2022 GMT
    *  expire date: Sep 13 05:29:02 2022 GMT
    *  subjectAltName: host "app.tnexample.com" matched cert's "*.tnexample.com"
    *  issuer: C=US; O=Let's Encrypt; CN=R3
    *  SSL certificate verify ok.
    * Using HTTP2, server supports multiplexing
    * Connection state changed (HTTP/2 confirmed)
    * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
    * Using Stream ID: 1 (easy handle 0x152811400)
    > GET / HTTP/2
    > Host: app.tnexample.com
    > user-agent: curl/7.79.1
    > accept: */*
    > 
    ...
    < HTTP/2 200 
    ...
    < content-type: text/html; charset=utf-8
    < content-length: 99
    < x-powered-by: Express
    < etag: W/"63-YG5NFOcPJdtrKh8AoAwX12ItC28"
    < strict-transport-security: max-age=15724800; includeSubDomains
    < 
    Hello world from hello-world-deployment-69c5c5cd86-pvdll! Your app is up and running in a cluster!
    * Connection #0 to host app.tnexample.com left intact
    Verify by accessing your app with the Ingress subdomain and the path https://CUSTOM-DOMAIN-HOST/APP-PATH, and you should see the HTTPS connection is established with the valid custom domain certificate:
    Verify by accessing your app with the Ingress subdomain and the path https://CUSTOM-DOMAIN-HOST/APP-PATH, and you should see the HTTPS connection is established with the valid custom domain certificate:

Learn more

I hope that you now understand how you can configure a TLS certificate with a custom domain in a Kubernetes cluster so that you can access an application with the custom domain. If you want to learn more, the following links will help:

Be the first to hear about news, product updates, and innovation from IBM Cloud