This tutorial shows how to install K3s with nginx, cert manager and ArgoCD

  • K3s
    • Lightweight, easy to install Kubernetes distribution, Docs: https://k3s.io/
    • Use latest Debian/Ubuntu Image as base

Basics (K3s, K9s, Helm)

  • ☸️ K3s (without traefik)

    K3s comes with traefik per default, but we want to use nginx ingress

    curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable=traefik" sh
    # needed for K9s and helm
    export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
    
  • 🔧 K9s (fany K8s management tool)

    curl -sS https://webinstall.dev/k9s | bash
    source ~/.config/envman/PATH.env
    
  • 🪖 Helm (K8s package manager)

    curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
    

Nginx Ingress & Cert Manager

  • 🔒 Cert Manager (K8s add-on automates management & renewal of TLS certificates)

    https://cert-manager.io/docs/installation/ (find latest version)

    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml
    
  • 🔒 Cluster Issuer (for Cert Manager)

    # letsencrypt-prod.yaml
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
    spec:
      acme:
        # The ACME server URL
        server: https://acme-v02.api.letsencrypt.org/directory
        # Email address used for ACME registration
        email: **your@mail.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:
                ingressClassName: nginx
    
    kubectl apply -f letsencrypt-prod.yaml
    
  • 🚪 Nginx Ingress (manages external access to K8s services)

    https://kubernetes.github.io/ingress-nginx/deploy/

    The last line sets Nginx as default ingress class (different to installation guide from official docs)

    helm upgrade --install ingress-nginx ingress-nginx \
      --repo https://kubernetes.github.io/ingress-nginx \
      --namespace ingress-nginx --create-namespace \
      **--set controller.ingressClassResource.default=true**
    
    • Output with example ingress yaml

      Release "ingress-nginx" does not exist. Installing it now.
      NAME: ingress-nginx
      LAST DEPLOYED: Sat Nov 16 18:06:51 2024
      NAMESPACE: ingress-nginx
      STATUS: deployed
      REVISION: 1
      TEST SUITE: None
      NOTES:
      The ingress-nginx controller has been installed.
      It may take a few minutes for the load balancer IP to be available.
      You can watch the status by running '**kubectl get service --namespace ingress-nginx ingress-nginx-controller --output wide --watch**'
      
      An example Ingress that makes use of the controller:
        **apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: example
          namespace: foo
        spec:
          ingressClassName: nginx
          rules:
            - host: www.example.com
              http:
                paths:
                  - pathType: Prefix
                    backend:
                      service:
                        name: exampleService
                        port:
                          number: 80
                    path: /
          # This section is only required if TLS is to be enabled for the Ingress
          tls:
            - hosts:
              - www.example.com
              secretName: example-tls**
      
      If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
      
        **apiVersion: v1
        kind: Secret
        metadata:
          name: example-tls
          namespace: foo
        data:
          tls.crt: <base64 encoded cert>
          tls.key: <base64 encoded key>
        type: kubernetes.io/tls**
      

Argo

  • 🐙 ArgoCD (GitOps-based Continuous Delivery tool)

    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    
  • 🔑 Argo initial admin password

    The initial admin password is stored in a secret, we need to get it and decode with base64. The default username is admin

    kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
    

    You can also use K9s to get the password. Select all namespaces or the argocd namespace to see it.

  • 🕸️ Argo Ingress (to access the argo web interface)

    argo-ingress.yaml

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: argocd
      namespace: argocd
      annotations:
        cert-manager.io/cluster-issuer: **letsencrypt-prod**
    spec:
      ingressClassName: nginx
      rules:
        - host: **argocd.your-domain.io**
          http:
            paths:
              - pathType: Prefix
                backend:
                  service:
                    name: argocd-server
                    port:
                      number: 80
                path: /
      tls:
        - hosts:
          - **argocd.your-domain.io**
          secretName: argocd-tls
    
    kubectl apply -f argo-ingress.yaml
    

    Check status of Certificate Request

    Let’s check the progress of the Letsencrypt ACME challenge. Use K9s or this command:

    kubectl get certificaterequests -A -o wide
    
    NAMESPACE   NAME           APPROVED   DENIED   READY   ISSUER             REQUESTER                                         STATUS                                         AGE
    argocd      argocd-tls-1   True                True    letsencrypt-prod   system:serviceaccount:cert-manager:cert-manager   **Certificate fetched from issuer successfully**   20m
    

    ERR_TOO_MANY_REDIRECTS

    You try to access the page and get an ERR_TOO_MANY_REDIRECTS error. What happened?

    If you search the web, you will quickly find the solution: https://github.com/argoproj/argo-cd/issues/2953#issuecomment-602898868

    We have to set the insecure config to true and restart the deployment

    kubectl patch configmap argocd-cmd-params-cm -n argocd --type=merge -p '{"data":{"server.insecure":"true"}}'
    
    kubectl rollout restart deployment argocd-server -n argocd
    # it will take some time, maybe run the restart command again
    

    Access the page

    Now you can access the page with the domain you specified

  • GitLab/GitHub Repo anlegen

    brew install argocd
    argocd login argocd.your-domain.com
    

~/.bashrc

  • 🐚 Shell

    To enable autocompletion for the kubectl command, use k as alias and set the KUBECONFIG environment variable (for K9s and helm)

    nano ~/.bashrc
    
    # Enable bash completion (remove comments)
    if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
        . /etc/bash_completion
    fi
    
    # Kubectl (completion, k alias)
    source <(kubectl completion bash)
    alias k=kubectl
    complete -F __start_kubectl k
    
    # Helm
    export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
    source <(helm completion bash)
    

Deploy Hello World App (without ArgoCD)

You need a domain for the ingress. Just point one to the external IP of the VM. If you don’t have a domain, you can use https://nip.io/

  • hello-deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-world
      labels:
        app: hello-world
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: hello-world
      template:
        metadata:
          labels:
            app: hello-world
        spec:
          containers:
          - name: hello-world
            image: nginx:latest
            ports:
            - containerPort: 80
    
  • hello-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: hello-world-service
    spec:
      selector:
        app: hello-world
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
      type: ClusterIP
    
  • hello-ingress.yaml

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: hello-world-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
    spec:
      rules:
      - host: hi.example.com  # Replace with your domain
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-world-service
                port:
                  number: 80
    
  • kubectl apply all the yaml files

    # all files in folder
    kubectl apply -f ./
    # or one by one
    kubectl apply -f hello-deployment.yaml
    

    Note: In the ingress yaml is no ingressClassName specified, so it will use the default ingress class. To implecitly set the ingress class, use this ingress.yaml:

    • hello-ingress.yaml

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: hello-world-ingress
        annotations:
          nginx.ingress.kubernetes.io/rewrite-target: /
      spec:
        **ingressClassName: nginx  # Explicitly specify nginx IngressClass**
        rules:
        - host: hi.example.com
          http:
            paths:
            - path: /
              pathType: Prefix
              backend:
                service:
                  name: hello-world-service
                  port:
                    number: 80
      
      kubectl apply -f hello-ingress.yaml
      

You can now visit the Nginx Hello World Page via HTTP e.g. with curl

curl -v hi.**you-domain.com**
# ...
# <!DOCTYPE html>
# <html>
# <head>
# <title>**Welcome to nginx**!</title>
# ...