Deploying a K3s cluster with SSL
Tags: apisix, kubernetes, apigateway, k3s
Published: 2024-04-24 06:00:14

---
title: Deploying a K3s cluster with SSL
published: true
date: 2024-04-20 00:00:00 UTC
tags: apisix,kubernetes,apigateway,k3s
canonical_url: https://jorge.aguilera.soy/blog/2024/k3s-ssl.html
---

In this post I’ll explain how to deploy a ready to prod k8s cluster (with only 1 node) using k3s and exposing a service using https with an auto-provisioned certificate using Let’s Encrypt

As a plus we’ll install Apisix, an OpenSource ApiGateway, to allow grow up our stack with more nodes+applications

## Requirement

- A Linux server with (min) 2Gb

- A registered DNS, for example `api.jorge.io`

- kubectl installed

- helm installed

## Install k3s

K3s is a light implementation of kubernetes ready to production. You can find more information at [https://docs.k3s.io/installation](https://docs.k3s.io/installation)

`curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--tls-san api.jorge.io" sh -`

## Install Apisix

Apisix is an ApiGateway. You can use it as standalone, with docker or integrate into your cluster. It has a lot of plugins and features ready to be used in production

We’ll use a namespace for Apisix:

`kubectl create namespace ingress-apisix`

We’ll install a basic/typical implementation:

```
helm repo add apisix https://charts.apiseven.com
helm update
KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm install apisix apisix/apisix --set gateway.tls.enabled=true --set ingress-controller.enabled=true --namespace ingress-apisix
```

## Install CertManager

CertManager is the agent able to create and provision certificates. It allows to create diferent kinds of certificates and talk with different CAs to create and install them as secrets into our cluster. For our we’ll use it to install free SSL certificates from Let’s Encrypt

```
helm repo add jetstack https://charts.jetstack.io --force-update
helm update
KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.14.4
```

## Checking

We’ll check if Apisix is ready

`kubectl get all -n ingress-apisix`

Pods and services are up and running

## The funny part

First thing (can be also last, of course, it doesnt matter) will be to redirect all plain http requests to https

traefik-https-redirect-middleware.yaml

```
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
    name: redirect-https
spec:
    redirectScheme:
        scheme: https
        permanent: true
```

`kubectl apply -f traefik-https-redirect-middleware.yaml`

Next, we’ll create the issuer:

cluster-issuer.yaml

```
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
    name: letsencrypt
    namespace: ingress-apisix
spec:
    acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: jorge@edn.es
        privateKeySecretRef:
            name: letsencrypt
        solvers:
        - selector: {}
          http01:
            ingress:
            class: traefik
```

`kubectl apply -f cluster-issuer.yaml`

We’ve created a ClusterIssuer (so all nodes and all namespaces in the cluster will be able to use it)

It will negociate with Lets Encrypt using the `http01` method via traefik and will create a secret into the cluster named `letsencrypt` (or whatever you specify)

Now, we’ll send all trafic to Apisix

ingress.yml

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    annotations:
        spec.ingressClassName: traefik
        cert-manager.io/cluster-issuer: letsencrypt
        traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd
    name: api-jorge-ingress
    namespace: ingress-apisix
spec:
    rules:
    - host: api.jorge.io
      http:
        paths:
        - pathType: Prefix
            path: /
            backend:
                service:
                    name: apisix-gateway
                    port:
                        number: 80
    tls:
    - hosts:
        - api.jorge.io
      secretName: letsencrypt-cert
```

`kubectl apply -f ingress.yml`

Pay attention to the annotations, this is the "trick":

- we will use the traekif ingress

- we’re instructing to the certificate manager to use the previous issuer created

- we’re instructing to traefil to redirect all plain http to https

- we’re instructing we want to use a certificate from a secret (created by cert-manager)

**Now all traffic will be served from our k3s cluster using https with a certificate created by Lets’Encrypt**

Obviously we’ll have a 404 as Apisix doesn’t know what to do with the request

## Deploying a service

We’ll deploy a typical service (httpbin)

`kubectl run httpbin --image kennethreitz/httpbin --namespace ingress-apisix`

(We are creating a deploying httpbin using the public image kennethreitz/httpbin)

and exposing it

`kubectl expose pod httpbin -n ingress-apisix --port 80`

It only remains to "link" Apisix with the new httpbin service:

routes.yaml

```
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
    name: httpbin
spec:
    http:
    - name: httpbin
      match:
        paths:
        - /*
        hosts:
            - api.jorge.io
      backends:
        - serviceName: httpbin
          servicePort: 8080
```

We are creating an ApisixRoute to route all requests to `api.jorge.io` to the httpbin service.

And this is all. Right now a request from outside will follow:

- traefik redirecting http to https

- cert-manager negociate, creates and store the certificate

- traefik redirect all requests to Apisix

- Apisix determine which service to use (httpbin in our case)

- httpbin response to the requests