Skip to content

Post k8s installation config

After k8s installation, you need to install other tools such as: - helm - reverse proxy

I consider cni plugin (e.g. calico, flannel, etc.) is a part of basic component of k8s cluster.

1. Install helm

The official release page of helm can be found here

You can use the below bash script to install the latest version.

vim install_helm.bash

# put the below content and run it
bash install_helm.bash

#!/bin/bash

set -euo pipefail

HELM_VERSION="v3.18.4"
HELM_TAR="helm-${HELM_VERSION}-linux-amd64.tar.gz"
HELM_URL="https://get.helm.sh/${HELM_TAR}"
TMP_DIR="/tmp/helm-install"
HELM_DIR="linux-amd64"

mkdir -p "$TMP_DIR"
cd "$TMP_DIR"

echo "Downloading Helm ${HELM_VERSION} to ${TMP_DIR}..."
wget -q "${HELM_URL}"

echo "Extracting Helm..."
tar -xzf "${HELM_TAR}"

echo "Setting execution permission..."
chmod a+x "${HELM_DIR}/helm"

echo "Moving Helm binary to /usr/local/bin..."
sudo mv "${HELM_DIR}/helm" /usr/local/bin/helm

echo "Cleaning up..."
rm -rf "$TMP_DIR"

echo "Verifying Helm installation..."
if command -v helm >/dev/null 2>&1; then
    helm version
    echo "Helm installed successfully."
else
    echo "Helm installation failed."
    exit 1
fi

You should see the helm version and the success message. You need to change version and target system architecture if you use another OS other than debian 11.

2. Set up a reverse proxy

A reverse proxy is essential for a k8s cluster. Otherwise, the applications deployed in the cluster are not accessible from the outside world. There are many possible reverse proxy solutions such as: - Kong (commercial alternative): https://konghq.com/ - Traefik (commercial alternative): https://traefik.io/

In this tutorial, we choose ingress-nginx.

2.1 The ingress nginx controller mode

There are three modes to set up the proxy and reverse proxy for a k8s cluster: - host - load balancer - nodePort

2.1.1 Host mode

In host mode, the Ingress controller it uses the host's network namespace. This means that the Ingress controller binds directly to the host's network interfaces and ports.

The advantage of the host mode is that it can achieve higher performance compared to other modes, as it eliminates the overhead of the kube-proxy layer.

The disadvantage is that you cannot run multiple instances of the Ingress controller on the same host with the same ports, as there would be port conflicts.

To view the detailed configuration of host mode, check the section of hostNetwork: true section in the values.yaml template.

2.1.2 Load balancer mode

In the load balancer mode, the Ingress controller typically runs as a service, and an external load balancer (normally provided by the cloud provider) is provisioned to distribute incoming traffic to the Ingress controller service.

This mode is suitable for cloud environments where a load balancer service can be provisioned dynamically (e.g., AWS ELB, GCP Load Balancer). The external load balancer takes care of distributing traffic to the nodes running the Ingress controller service.

To view the detailed configuration, check the section of appProtocol:True section

2.1.3 nodePort mode

In NodePort mode, the Ingress controller service is exposed on a static port on each node in the cluster. This port is accessible from outside the cluster, and the traffic is then forwarded to the Ingress controller.

This mode is often used in on-premises or bare-metal environments where cloud load balancers are not available or in development/testing scenarios.

While it provides external access, it might not be as suitable for production environments due to potential challenges in scaling and managing external access.

3. A real example

In this example, we choose the host mode. So the ingress-nginx listens to the network interface of the host server. As a result, only one ingress nginx pod can be deployed on each node. And we don't want to have more than one ingress. So we added a node selector on the ingress nginx controller service is only deployed on a specific node.

3.1 Select which node to deploy the ingress nginx controller

To deploy ingress nginx controller service on a specific node: - Label a worker node with a specific label (e.g. ingress-node) - Add a node selector on the ingress nginx controller service

We label only one node, because we need to set up a dns resolver entry, so all the incoming querier can be redirected to the node which contains the ingress controller.

Even we have two pods of Ingress controller. The second one that is not in the DNS will never be used.

# our k8s cluster 
- Master Node (k8s-master)  10.50.5.67
- Worker Node 1 (k8s-worker1)  10.50.5.68
- Worker Node 2 (k8s-worker2)  10.50.5.69

# Here we choose worker 1 to host ingress
# FQDN for k8s Ingress controller
10.50.5.68   *.casd.local

3.2 Label the node

The below commands show you how to label a node with a specific label

# get all available nodes
kubectl get nodes

# output example
NAME            STATUS   ROLES           AGE   VERSION
onyxia-master   Ready    control-plane   19h   v1.33.2
onyxia-w01      Ready    <none>          19h   v1.33.2
onyxia-w02      Ready    <none>          19h   v1.33.2

# general form to label a node, 
kubectl label node <nodename> <label-key>=<label-value>

# example
kubectl label node onyxia-w01 ingress-node=true

# to un-label a node, you can use the below command
kubectl label node <nodename> <labelname>-

# example
kubectl label node worker2 public-

# after labeling, you will see new pod of nginx gets created.
kubectl get all -n ingress-nginx -w
kubectl get pods -n ingress-nginx -w

3.3 Deploy the ingress nginx controller service

You can use the below commands to deploy the ingress nginx controller service

# we want it to run is the namespace ingress-nginx, so we create a namespace
kubectl create namespace ingress-nginx

# add ingress-nginx helm repo
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# update the repo content
helm repo update

# list available release 
helm search repo

# output example
NAME                            CHART VERSION   APP VERSION     DESCRIPTION
ingress-nginx/ingress-nginx     4.13.0          1.13.0          Ingress controller for Kubernetes using NGINX a

3.3.1 Configure the ingress-nginx controller

With the above ingress-nginx repo, we can install an ingress-nginx controller service in our cluster.

You can find the full doc https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/

You can find the values.yaml template here https://github.com/kubernetes/ingress-nginx/blob/main/charts/ingress-nginx/values.yaml

3.3.2 A minimum config example

Note the below ingress_values.yaml is an example of how our cluster configures the ingress-nginx controller.

controller:
  watchIngressWithoutClass: true
  allowSnippetAnnotations: false
  config:
    error-log-level: "info"
    ignore-invalid-headers: "false"
    proxy-request-buffering: "off"
    proxy-body-size: "0"
    large-client-header-buffers: "4 16k"

  hostNetwork: true
  extraEnvs:
    - name: MY_POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
  kind: DaemonSet
  service:
    enabled: true
    type: ClusterIP
  ingressClassResource:
    name: nginx
    enabled: true
    default: true
    controllerValue: "k8s.io/ingress-nginx"

rbac:
  create: true
podSecurityPolicy:
  enabled: false

With the above configuration, you can have a minimum running ingress controller

3.3.3 Deploy the ingress service

# we deploy the ingress service with above
# here the version is the helm chart version.
helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress_values.yaml -n ingress-nginx --version v4.13.0

# get all components of the ingress-nginx
kubectl get all -n ingress-nginx

# output example
NAME                                 READY   STATUS    RESTARTS   AGE
pod/ingress-nginx-controller-lnd4s   1/1     Running   0          81s

NAME                                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/ingress-nginx-controller             ClusterIP   10.96.43.173     <none>        80/TCP,443/TCP   81s
service/ingress-nginx-controller-admission   ClusterIP   10.111.211.231   <none>        443/TCP          81s

NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                              AGE
daemonset.apps/ingress-nginx-controller   1         1         1       1            1           ingress-node=true,kubernetes.io/os=linux   81s

After the pod of ingress service is created, you can try to send a request to the ip of service/ingress-nginx-controller.

# In our example, the ip address of the service is 10.96.43.173, you can try below command
curl 10.96.43.173 

# if you see below output, it means ingress-nginx is running and answering request
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

ingress nginx cve-2025-1974: https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/ Avoid the versions which are affected by this CVE.

3.3.4 Test ingress with an application

You can try to deploy the mario app and check the certificate.

The full manifest can be found here

# copy the three yaml files in a folder, then run
kubectl apply -f .

If your dns is configured to redirect queries to the ingress service, then you should be able to use the url to access the service.

You will notice, ingress assigns a fake certificat. Because we have not configured a valid certificate for ingress.

We need to replace this fake certificate with a valid certificate.

4. Configure nginx with a custom certificate for all services

In this tutorial, we suppose you only have: - a self-signed root CA certificate. - a wildcard certificate signed by the root CA.

The objectives are: - Ingress controller trusts the root CA, so it can validate certificates signed by it (for TLS termination). - All apps use the wildcard certificate (e.g., *.casd.local) signed by that internal root CA. - TLS is terminated at the ingress level, and the root CA is the trusted anchor.

4.1 Check your certificates

You need to have the below certificates: - root ca: - wildcard certificate signed by ca - wildcard certificate private key

4.2 Create a TLS secret with the wildcard certificate

Create a secret to host the certificate and private key. we name the secret as casd-wildcard-certificate, you can use the below command

# general form
kubectl create secret tls <secret-name> --namespace <namespace-name> --key=pathTo/ingress-tls.key --cert=pathTo/ingress-tls.crt -o yaml

# example
kubectl create secret tls casd-wildcard-certificate --key=wildcard-casd.key --cert=wildcard-casd.crt -o yaml -n ingress-nginx 

# view the content of the secret, the certificate and private value is in base64, you need to decode it to view the
# value. No encryption at all, so we need to pay attention on who can view this secret.
kubectl get secret casd-wildcard-certificate -o jsonpath='{.data}' -n ingress-nginx 

# you can also edit the value directly
kubectl edit secret casd-wildcard-certificate -n ingress-nginx 

4.3 Create a secret for root ca

# Create a Secret for the root CA
kubectl create secret generic casd-root-ca \
  --from-file=ca.crt \
  -n ingress-nginx

# check the created secret 
kubectl get secret casd-root-ca -o jsonpath='{.data}' -n ingress-nginx 

4.4 Mount Root CA and wildcard certificate into NGINX controller

To tell the ingress to use the given certificate, you need to use extraArgs.default-ssl-certificate config. Below is a full example. Then you need to update the ingress controller with new configuration

controller:
  # the ingress controller will process all Ingress resources that do not have an ingressClassName field.
  watchIngressWithoutClass: true
  allowSnippetAnnotations: false

  # use node selector to install nginx on a specific node
  # all nodes that have label ingress-node:"true" will have a replicas of the nginx 
  nodeSelector:
    ingress-node: "true"

  config:
    error-log-level: "info"
    ignore-invalid-headers: "false"
    proxy-request-buffering: "off"
    proxy-body-size: "0"
    large-client-header-buffers: "4 16k"
    # This tells NGINX to verify the TLS certificate presented by the upstream (backend) service.
    # default value is off
    proxy-ssl-verify: "on"
    # Specifies the CA certificate NGINX should use to verify the backend service’s TLS certificate
    proxy-ssl-trusted-ca-file: "/etc/nginx/certs/ca.crt"
    # tells NGINX which CA to use when verifying client certificates, i.e., when a client presents a certificate to authenticate itself.
    ssl-trusted-ca-file: "/etc/nginx/certs/ca.crt"

  hostNetwork: true

  extraEnvs:
    - name: MY_POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP

  kind: DaemonSet

  service:
    enabled: true
    type: ClusterIP

  ingressClassResource:
    name: nginx
    enabled: true
    default: true
    controllerValue: "k8s.io/ingress-nginx"
    # Parameters is a link to a custom resource containing additional
    # configuration for the controller. This is optional if the controller
    # does not require extra parameters.
    parameters: {}

  # Set global default TLS certificate (wildcard)
  # no need to use  `- secretName: casd-test-tls-secret` in ingress.yaml
  # to specify a custom certificate
  # the default certificate should be a wildcard which covers your domain
  extraArgs:
    default-ssl-certificate: "ingress-nginx/casd-wildcard-certificate"

  # Mount internal CA certificate
  extraVolumeMounts:
    - name: root-ca
      mountPath: /etc/nginx/certs
      readOnly: true

  extraVolumes:
    - name: root-ca
      secret:
        secretName: casd-root-ca

rbac:
  create: true

podSecurityPolicy:
  enabled: false

Make sure you have the wildcard and root-ca certificate secret in ingress-nginx name space.

4.5 Update existing ingress-nginx deployment

The best way to update a deployment (deployed via helm chart) is to modify the values.yaml. Then call the below command

# general form
helm upgrade <deployment-name> <chart-name> -f <config-file> -n <namespace>

# example
helm upgrade ingress-nginx ingress-nginx/ingress-nginx -f ingress_values.yaml -n ingress-nginx

# to delete 
helm delete ingress-nginx -n ingress-nginx