Introduction

If you have created an application, you will need to expose it to the outside world through HTTPS. If you're working working with Kubernetes to deploy your application, you have powerful tools such as cert-manager and LetsEncrypt that can help you achieve this.

Throughout this tutorial, we will be setting everything up from scratch. This means the following guide can be applied to any Kubernetes cluster, but by using Civo Kubernetes it becomes easier as you have ready-to-use marketplace applications that will make things easier.

If you would like to try this on your own cluster that's not on Civo, you will need to install Traefik and cert-manager on your own. In this tutorial, I will add the installations commands separately for both Traefik and cert-manager.

Civo Kubernetes cluster creation

The first step of this process is to have a Kubernetes cluster created. For this tutorial, I will be using Civo Kubernetes as it is super fast with a <2 minute launch time. This will also allow me to utilize the power of Civo Marketplace which means I can install my favourite Kubernetes applications in seconds.

I will be using Civo CLI to create the cluster, which can be installed from here. If you would prefer to use the Civo web interface, you can do that too.

On the CLI, let's create our cluster:

civo k3s create https-civo --remove-applications=Traefik-v2-nodeport --applications Traefik-v2-loadbalancer,cert-manager
The cluster https-civo (45fab9fd-daaa-41ae-a1e8-7642d97f1c6b) has been created

The above example will create a 3 node cluster named https-civo, and install Traefik in loadbalancer mode, as well as cert-manager at the same time. Once the cluster is up and running in a couple of minutes, we can proceed.

We will then need to get the Kubeconfig for the cluster and save it to our desired location. If you do not specify a path, it will save it to the default location of ~/.kube/config:

civo k3s config https-civo --save --local-path /Users/saiyam/Downloads/https-civo.config
Merged with main kubernetes config: /Users/saiyam/Downloads/https-civo.config

Let's make sure that kubectl knows to use our cluster's configuration file. Make sure you specify the path where you saved your configuration file:

$ export KUBECONFIG=/Users/saiyam/Downloads/https-civo.config

$ kubectl get nodes
NAME                                              STATUS   ROLES    AGE     VERSION
k3s-https-civo-4ba6-29bb47-node-pool-7071-t6g2y   Ready    <none>   2m14s   v1.22.11+k3s1
k3s-https-civo-4ba6-29bb47-node-pool-7071-fi3vh   Ready    <none>   94s     v1.22.11+k3s1
k3s-https-civo-4ba6-29bb47-node-pool-7071-5djri   Ready    <none>   13s     v1.22.11+k3s1

We can also check if the Traefik Loadbalancer and cert-manager components are installed:

$ kubectl get all -n kube-system | grep traefik
pod/helm-install-traefik-crd-sjmk5   0/1     Completed   0          3m4s
pod/helm-install-traefik-2f6dq       0/1     Completed   2          3m4s
pod/traefik-znjw7                    1/1     Running     0          2m15s
pod/traefik-svwxg                    1/1     Running     0          2m15s
pod/traefik-6nqhm                    1/1     Running     0          59s
service/traefik          LoadBalancer   10.43.157.236   74.220.22.175   80:32429/TCP,443:32409/TCP   2m16s
daemonset.apps/traefik         3         3         3       3            3           <none>          2m17s
$ kubectl get pods -n cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-848f547974-z88kf             1/1     Running   0          3m39s
cert-manager-cainjector-54f4cc6b5-n26m5   1/1     Running   0          3m39s
cert-manager-webhook-7c9588c76-xrrq9      1/1     Running   0          3m39s

If you are not using Civo Kubernetes, you need to install Traefik following the documentation and make sure you have some arguments passed such as the ones below:

        - args:
          - --global.checknewversion
          - --global.sendanonymoususage
          - --entrypoints.metrics.address=:9100/tcp
          - --entrypoints.traefik.address=:9000/tcp
          - --entrypoints.web.address=:8000/tcp
          - --entrypoints.websecure.address=:8443/tcp
          - --api.dashboard=true
          - --ping=true
          - --metrics.prometheus=true
          - --metrics.prometheus.entrypoint=metrics
          - --providers.kubernetescrd
          - --providers.kubernetesingress
          - --providers.kubernetesingress.ingressendpoint.publishedservice=kube-system/traefik
          - --entrypoints.websecure.http.tls=true

To install cert-manager to a non-Civo Kubernetes cluster, you can run the below command:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml

Now you should have a Kubernetes cluster with Traefik and cert-manager installed automatically if using Civo Kubernetes and selecting the apps during installation or manually using the mentioned methods above.

Create a ClusterIssuer

In order to get cert-manager to issue us a valid certificate, you need to create a Cluster Issuer resource in your cluster.

Change the email address in the snippet below, save it as clusterissuer.yaml and then apply the YAML file to the cluster with kubectl apply -f clusterissuer.yaml:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: saiym911@gmail.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: traefik

Once this has been applied you should see clusterissuer.cert-manager.io/letsencrypt-prod created returned from the cluster.

Create a certificate

For this step, you need to use your Traefik DNS or website configuration to create a certificate. When using Civo and the Civo CLI, you can find this out using the civo k3s show CLUSTER_NAME command and get the information from the Load balancers section shown in the output. It should look something like this:

Loadbalancers:
+--------------------------------+-------------+---------------+--------------+-----------+--------------------------------------+--------------------------------------------------+
| Name                           | Algorithm   | Public IP     | Private IP   | State     | Firewall                             | DNS Name                                         |
+--------------------------------+-------------+---------------+--------------+-----------+--------------------------------------+--------------------------------------------------+
| https-civo-kube-system-traefik | round_robin | 74.220.22.175 | 192.168.1.52 | available | c9e14ae8-b8eb-4bae-a687-9da4637233da | 0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com |
+--------------------------------+-------------+---------------+--------------+-----------+--------------------------------------+--------------------------------------------------+

Use the DNS name in the commonName and dnsNames sections of this, the Certificate resource definition:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: demo
  namespace: default
spec:
  secretName: demo
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com
  dnsNames:
  - demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com

Save the file as certificate.yaml and apply it to your cluster with kubectl apply -f certificate.yaml.

Once this has been applied, you will get the result: certificate.cert-manager.io/demo created

Create the demo nginx app

Next, you will need to create the demo nginx application with a clusterIP service. This is the application that will be exposed on our cluster over HTTPS. Save the following as application.yaml:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx-demo
  namespace: default
  labels:
    app: nginx-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx
        image: "nginx"
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo
  namespace: default
spec:
  selector:
    app: nginx-demo
  ports:
  - name: http
    targetPort: 80
    port: 80

You will need to apply this to your cluster, with kubectl apply -f application.yaml.

Once this is applied, you should be able to check for the application pod and service coming up with the relevant commands:

$ kubectl get pods 

NAME                          READY   STATUS    RESTARTS   AGE
nginx-demo-7d56b74b84-h6zww   1/1     Running   0          80s

$ kubectl get svc

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1       <none>        443/TCP   16m
nginx-demo   ClusterIP   10.43.130.181   <none>        80/TCP    84s

Create the ingress

Now is the time to create the ingress that will allow the application to be accessed from outside the cluster. Save the following definition YAML file, making sure you edit in the host to match your cluster's details:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: traefik
  name: nginx-ingress
  namespace: default
spec:
  rules:
  - host: demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com
    http:
      paths:
      - backend:
          service:
            name: nginx-demo
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com
    secretName: demo 

Once applied, you will be able to see that the ingress has been created by the following result:

$ kubectl get ing
NAME            CLASS    HOSTS                                                   ADDRESS         PORTS     AGE
nginx-ingress   <none>   demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com   74.220.22.175   80, 443   15s

Now, it's time to hit the HTTPS endpoint and feel the joy of exposing your application to the internet over HTTPS. You'll need to run the command curl -k https://demo.your-cluster-loadbalancer-id.lb.civo.com as follows:

$ curl -k https://demo.0d652485-6bb6-465f-b3b3-eb12485d9aee.lb.civo.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Or, you could hit the URL with your web browser:

NginX web server welcome message displayed with the address of the Civo load balancer visible. The browser recognises the connection is secured.

You can see that the connection has been over HTTPS and the browser recognises the certificate as valid.

Conclusion

Now that we have reached the end of this tutorial, you have learned how to expose your applications to the internet with HTTPS using Civo Kubernetes, Traefik, and cert-manager (which can be installed via Civo Marketplace).

We would love to hear what other methods you use to expose applications over HTTPS? Let us know and we would be happy to add a post about it.