---
# System prepended metadata

title: Kubernetes Netcup - USEFLEDS
tags: [usefleds, infrastructure]

---

# Kubernetes Cluster on Netcup for USEFLEDS

## Infos

**Nodes:** (details on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/servers))
* Master: k8s-0.oydapp.eu (v22019071530492888)
  intern: k8s-0 und k8s-master.local, IP: `152.89.104.40`
* Worker:
  * k8s-1.oydapp.eu (v22019071530492886)
    intern: k8s-1 und k8s-worker01.local, IP: `152.89.105.62`
  * k8s-2.oydapp.eu (v22019071530492887)
    intern: k8s-2 und k8s-worker02.local, IP: `152.89.107.39`
* Storage: 
  * IP: `37.120.184.76` (v220200315304112158)

## Baremetal Setup

Resources:
* [How to Install Kubernetes Cluster on Debian](https://www.linuxtechi.com/install-kubernetes-cluster-on-debian/)
* kubeadm init: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/
* login to Server Control Panel to deploy Linux image: https://www.servercontrolpanel.de/SCP/Home

Steps:
1) deploy latest Debian Image with minimial setup
    (don't configure user or SSH keys; afterwards login using ssh with provided root password)
2) install Kubernetes Cluster
    <details><summary>Configure accounts</summary>
  
    ```bash=
    # configure vi as default editor
    update-alternatives --config editor
    # enable passwordless sudo
    visudo # -> update at bottom: %sudo   ALL=(ALL:ALL) NOPASSWD:ALL
    ```
    for each user:
    ```bash=
    adduser christoph --disabled-password
    su christoph
    cd
    mkdir .ssh
    chmod 700 .ssh
    vi .ssh/authorized_keys # <- copy pubkeys
    vi .vimrc # add line: set nocompatible
    # and as root
    usermod -aG sudo christoph
    ```
    </details>
    <details><summary>Basic system configuration</summary>
      
    run as a user:
    ```bash=
    # install additional packages
    sudo apt-get update
    sudo apt-get -y install iptables-persistent gnupg gnupg2 curl software-properties-common
    # disable ssh root access with password
    sudo vi /etc/ssh/sshd_config # uncomment "PermitRootLogin prohibit-password"
    # restart sshd to apply changes
    sudo systemctl restart sshd
          
    # set hostname
    sudo hostnamectl set-hostname "k8s-xxx.local"
    sudo vi /etc/hosts
    
    152.89.104.40   k8s-master.local k8s-0
    152.89.105.62   k8s-worker01.local k8s-1
    152.89.107.39   k8s-worker02.local k8s-2
    
    # disable Swap
    sudo swapoff -a
    sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
    ```
    </details>
    <details><summary>Configure Firewall</summary>
      
    ```bash=
    cat << EOF | sudo tee /etc/iptables/rules.v4
    *filter
    :INPUT DROP [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]

    -A INPUT -s 193.170.140.34 -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT

    -A INPUT -p tcp -m state --state NEW -m tcp --dport 6443 -j ACCEPT
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT

    -A INPUT -s 152.89.105.62 -j ACCEPT
    -A INPUT -s 152.89.107.39 -j ACCEPT
    -A INPUT -s 152.89.104.40 -j ACCEPT
    -A INPUT -s 37.120.184.76 -j ACCEPT

    -A INPUT -i lo -j ACCEPT
    -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED,UNTRACKED -j ACCEPT
    COMMIT
    EOF

    cat << EOF | sudo tee /etc/iptables/rules.v6
    *filter
    :INPUT DROP [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]

    #-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED,UNTRACKED -j ACCEPT

    -A INPUT -i lo -j ACCEPT
    -A INPUT -p icmpv6 -j ACCEPT

    COMMIT
    EOF
    ```
    </details>
    <details><summary>Install and configure containerd</summary>
  
    ```bash=
    cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf 
    overlay 
    br_netfilter
    EOF

    sudo modprobe overlay
    sudo modprobe br_netfilter
    cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-k8s.conf
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1 
    net.bridge.bridge-nf-call-ip6tables = 1 
    EOF
      
    # to apply above changes:
    sudo sysctl --system
      
    # install conatinerd
    sudo apt-get -y install containerd
    
    # configure containerd so that it works with Kubernetes
    containerd config default | sudo tee /etc/containerd/config.toml >/dev/null 2>&1
      
    # set cgroupdriver to systemd: "SystemdCgroup = true"
    sudo vi /etc/containerd/config.toml
      
    # restart and enable containerd service
    sudo systemctl restart containerd
    sudo systemctl enable containerd
    ```
    </details>
    <details><summary>Install Kubernetes tools</summary>
  
    ```bash=
    # add Kubernetes Apt repository
    curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/cgoogle.gpg
    sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
      
    # install Kubernetes
    sudo apt-get install -y kubelet kubeadm kubectl
    sudo apt-mark hold kubelet kubeadm kubectl
    ```
    </details>
    <details><summary>Setup master node</summary>
  
    ```bash=
    sudo kubeadm init --control-plane-endpoint=k8s-0.oydapp.eu
    # save output for worker nodes to join
    
    # store kubeconfig
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    ```
    </details>
    <details><summary>Setup worker nodes</summary>

    update command with output from control-plan initialization on [Github](https://github.com/OYD-private/netcup-k8s/blob/main/servers/control-plane.txt)
    ```bash=
    sudo kubeadm join k8s-master.oydapp.eu:6443 --token xxx \
        --discovery-token-ca-cert-hash sha256:xxx
    ```
    </details>
    <details><summary>Setup Pod Network Using Calico on Master</summary>
  
    ```bash=
    command
    ```
    </details>

## Basic Kubernetes Setup
- [x] [Prerequisites](#Prequisites)
- [x] [Dashboard](#Dashboard-Installation)
- [x] [Ingress & Certiticates](#Ingress-amp-Certiticates)
- [x] [Postgres](#PostgreSQL-Cluster)
- sipped for now: LoadBalancer: https://metallb.universe.tf/installation/
- [ ] Monitoring

### Prequisites
* kubeconfig: [YAML on Github](https://github.com/OYD-private/netcup-k8s/blob/main/config.yml)

<details><summary>Tests</summary>
  
```bash=
# check version
kubectl --kubeconfig=config.yml version
# to skip --kubeconfig
export KUBECONFIG=/path/to/config.yml
kubectl get pods
```

</details>

<details><summary>Helper</summary>

shortcuts for `.bashrc` / `.zshrc`
```
export K8S_CONFIG="/path/to/config.yml"

# kubectl with config
k8() {
    kubectl --kubeconfig=$K8S_CONFIG "$@"
}
  
# get Pods matching first argument (are all)
k8p() {
    if [ -z "$1" ]; then
        kubectl --kubeconfig=$K8S_CONFIG get pods
    else
        kubectl --kubeconfig=$K8S_CONFIG get pods | grep $1
    fi
}
  
# get last 200 lines of log for first Pod matching argument
k8l() {
    kubectl --kubeconfig=$K8S_CONFIG logs -f --tail 200 `kubectl --kubeconfig=$K8S_CONFIG get pods | grep $1 | awk '{print $1}' | head -n 1`
}
  
# connect with a shell to the first Pod matching argument
k8c() {
    kubectl --kubeconfig=$K8S_CONFIG exec -it `kubectl --kubeconfig=$K8S_CONFIG get pods | grep $1 | awk '{print $1}' | head -n 1` -- bash
}
```
  
</details>

:::info 
all commands below assume helpers from above: `k8 ...`
:::
    
### Kubernetes & Server upgrade

* read the correct Howto for the version you want to upgrade:
https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/
    
The upgrade is always done by updating kubeadm first, then running sanity checks followed by an upgrade to the cluster. After that, the kubelet is upgraded by apt and restartet. During update and restart, the pods are migrated by kubectl drain and kubect uncordon commands, so that there is no outage. 
Update has to be done to control nodes first and then on the worker nodes.    
Draining nodes can also be used to apply system upgrades to the OS and reboot of the servers.
Skipping minor versions during upgrade is not supported, so the procedure may have to be repeated some times.  
    
<details><summary>Install (new) Kubeadm Repo</summary>
    
> commment out or remove old repo in /etc/apt/sources.list.d/archive_uri-http_apt_kubernetes_io_-bookworm.list
```
               
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
        
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
        
apt-get update        
```
change version in repo to the version of cluster you want to update to.
    
</details>

<details><summary>Prepare Cluster with kubeadm</summary>
replace x in 1.31.x-* with the latest patch version in the repository of /etc/apt/sources.list/
        
Upgrade only kubeadm and prevent furter automatic upgrades 
        
```
apt-mark unhold kubeadm && \
apt-get update && sudo apt-get install -y kubeadm='1.31.x-*' && \
apt-mark hold kubeadm
```
        
Check version of kubeadm, this should match the version from the repo. 
`kubeadm version`
        
Run diagnostic tests and see if cluster can be upgraded
`kubeadm upgrade plan`
        
if ok, upgrade cluster with the supplied command:
`kubeadm upgrade apply v1.31.x`

</details>
    
<details><summary>Upgrade kubelet packages and reload</summary>
this has to be done for control and worker nodes
    
at any time you can check the status and version of the cluster with:
     `    kubectl get nodes`
         
first drain the node so pods are started on the other nodes:
     `    kubectl drain <node-to-drain> --ignore-daemonsets`
         
upgrade the packages and prevent automatic upgrades:
replace x in 1.31.x-* with the latest patch version in the repository of /etc/apt/sources.list
```
         apt-mark unhold kubelet kubectl && \
         apt-get update && sudo apt-get install -y kubelet='1.31.x-*' kubectl='1.31.x-*' && \
         apt-mark hold kubelet kubectl
```
         
restart the service
    
```
         systemctl daemon-reload
         systemctl restart kubelet
```

Put node in service again
replace <node-to-uncordon> with the name of your node
 `       kubectl uncordon <node-to-uncordon> `    
</details>    
         
### Renew Certificats
* cluster usually renews certificats in time
  * check status with `sudo kubeadm certs check-expiration`
  * renew manually with `sudo kubeadm certs renew all`
* copy certificates to current user
  * home: `sudo cp /etc/kubernetes/admin.conf ~/.kube/config`
  * renew local: update `~/dev/deploy/netcup-k8s/config.yml`

### Dashboard Installation

Installation:
* deploy dashboard
  ```bash=
  k8 apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
  ```
* create user
  config on [Github `dashboard/`](https://github.com/OYD-private/netcup-k8s/tree/main/dashboard)
  ```bash=
  k8 apply -f serviceAccount.yml
  k8 create -f clusterRoleBinding.yml # might already exist
  ```

Access Dashboard:
* create token: `k8 -n kubernetes-dashboard create token admin-user`
* run `k8 proxy`
* [open Dashboard](http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/)

### Metrics Servcer
Info: https://github.com/kubernetes-sigs/metrics-server
config on [Github `metrics/`](https://github.com/OYD-private/netcup-k8s/tree/main/metrics) ([disabled TLS](https://github.com/OYD-private/netcup-k8s/blob/656a476562c9809b61ca2be51180ee67a10f8a65/metrics/high-availability-1.21%2B.yaml#L150) for now)
```bash=
k8 apply -f high-availability-1.21+.yaml
```
use:
```bash=
k8 top pod --all-namespaces --sort-by=memory
```
  
### Ingress & Certiticates
  
Steps:
* install [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
    config on [Github `ingress/`](https://github.com/OYD-private/netcup-k8s/tree/main/ingress) (requires to set external IP)
    ```bash=
    k8 apply -f ingress-nginx.yaml
    ```
* install [cert-manager](https://cert-manager.io/docs/installation/kubectl/)
    ```bash=
    k8 apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
    ```
* configure [issuer](https://cert-manager.io/docs/configuration/acme/)
      config on [Github `ingress/`](https://github.com/OYD-private/netcup-k8s/tree/main/ingress)
    ```bash=
    k8 apply -f letsencrypt-production.yaml
    ```
* deploy demo container
    config on [Github `ingress/`](https://github.com/OYD-private/netcup-k8s/tree/main/ingress)
    ```bash=
    k8 apply -f demo-pod.yaml
    ```

## PostgreSQL Cluster
Postgres-Operator Kubegres: https://www.kubegres.io

## Installation
* Github: https://github.com/reactive-tech/kubegres
* Docs: https://www.kubegres.io/doc/getting-started.html

### Basics
Steps:
* install Kubegres operator
  ```bash=
  k8 apply -f https://raw.githubusercontent.com/reactive-tech/kubegres/v1.17/kubegres.yaml
  ```
* create Secret resource
  ```bash=
  k8 apply -f postgres-secret.yaml
  ```

### Setup NFS Client Provisioner for Storage Class
config on [Github postgres/](https://github.com/OYD-private/netcup-k8s/tree/main/postgres) 
Steps:
* enable access of K8s nodes to storage server
  * login as root to storage server (`ssh user@37.120.184.76`and `sudo su`)
  * edit exports: `vi /etc/exports` and add a line with the IP of each node for the relevant mount point `/var/nfs/postgres`
    * 152.89.104.40
    * 152.89.105.62
    * 152.89.107.39
  * restart nfs-server: `systemctl restart nfs-kernel-server`
  * check access with: `showmount -e 37.120.184.76`
* install `nfs-common` package on each node
  `apt-get install nfs-common`
* deploy nfs-provisioner:
  ```bash=
  k8 apply -f namespace_nfs-provisioner.yaml
  k8 apply -f serviceaccount_nfs-provisioner.yaml
  k8 apply -f clusterrole_nfs-provisioner.yaml
  k8 apply -f clusterrolebinding_nfs-provisioner.yaml
  k8 apply -f deployment_nfs-provisioner.yaml

  #check
  k8 get pods -n nfs-provisioner
  ```
* create StorageClass
  ```bash=
  k8 apply -f storageclass.yaml
  ```

### Create a cluster of PostgreSql instances
currently using PostgreSQL v15.5 ([versions](https://www.postgresql.org/support/versioning/))
```bash=
k8 apply -f postgres.yaml
  
# check
k8 get pod,statefulset,svc,configmap,pv,pvc -o wide

# connect with psql
k8 exec -it postgres-1-0 -- psql -h postgres -U postgres -d postgres
```
Password is `superUserPassword` from `postgres-secret.yaml`
  
## Instances
  
### chats.go-data.at
  
```bash=
k8 set image deployment.v1.apps/chat-disp \
  chat-disp=oydeu/dc-intermediary:240129
```
  
### DEC112 Staging Environment
* use customised ingress: config on [Github `ingress/`](https://github.com/OYD-private/netcup-k8s/tree/main/ingress)
    ```bash=
    k8 apply -f ingress-nginx-dec112.yaml
    ```
* Redis deployment: config on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/redis)
    ```bash=
    k8 apply -f redis-config.yaml
    k8 apply -f redis-statefulset.yaml
    k8 apply -f redis-service.yaml
    ```
    to run `redis-cli` get Redis master password on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/redis/redis-config.yaml)
    ```bash=
    k8 exec -it redis-0 -- redis-cli
    auth secret-password-here
    pubsub channels #example command
    keys * #list all keys
    hgetall key #detail for key
    ```
* RegAPI deployment: config on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/regapi)
    ```bash=
    k8 apply -f regapi-secrets.yaml
    k8 apply -f regapi-deploy.yaml
    k8 apply -f sms-deploy.yaml
    k8 apply -f ida-deploy.yaml
    k8 apply -f sip-deploy.yaml
    ```
  
    updates:
    ```bash=
    k8 set image deployment.v1.apps/regapi regapi=oydeu/dc-regapi:240221a
    k8 set image deployment.v1.apps/sms-plugin sms-plugin=oydeu/dc-decsms:240221b
    k8 set image deployment.v1.apps/ida-plugin ida-plugin=oydeu/dc-decida:240221
    k8 set image deployment.v1.apps/sip-plugin sip-plugin=oydeu/dc-decsip:240221a
    ```
  
    quick test:
    ```bash=
    # init
    REGAPI="https://regapi.data-container.net"
    DID=`echo '' | oydid create --json --add-x25519pubkey-keyAgreement | jq -r '.did'`
    TOKEN=`oydid auth $DID $REGAPI --json | jq -r '.access_token'`
    # send SMS
    REG_ID=`echo '{"header": {"method": "sms","action": "init"},
         "payload":{"phone_number": "004367761753112",
                    "email": "christoph.fabianek@gmail.com"}}' | \
    curl -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" \
         -d @- -s \
         -X PUT $REGAPI/api/v3/register | \
    jq -r '.reg_id'`
    # enter SMS-Code
    CODE=12345678
    echo '{"header": {"method": "sms","action": "SmsVerificationCode"},
           "payload":{}}' | \
    jq --arg reg_id "$REG_ID" '.header += {"reg_id": $reg_id}' | \
    jq --arg code "$CODE" '.payload += {"sms_code": $code}' | \
    curl -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" \
         -d @- \
         -X PUT $REGAPI/api/v3/register
    # check state
    curl -H "Authorization: Bearer $TOKEN" \
         "$REGAPI/api/v3/register?reg_id=$REG_ID"
    ```
* Kamailio deployment: set ghcr.io and use config on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/kamailio)
    ```bash=
    k8 apply -f kamailio-deploy.yaml
    ```
* DEC112 Chatbot deployment: config on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/chatbot)
    ```bash=
    k8 apply -f chatbot-deploy.yaml
    ```
    update:
    ```bash=
    k8 set image deployment.v1.apps/dec112-chatbot \
      dec112-chatbot=oydeu/dc-chatbot:240211
    ```
* Wallet Onboarding deployment: config on [Github](https://github.com/OYD-private/netcup-k8s/tree/main/regapi)
    ```bash=
    k8 apply -f wallet-deploy.yaml
    ```
    update:
    ```bash=
    # update image
    k8 set image deployment.v1.apps/wallet-onboarding \
      wallet-onboarding=oydeu/dc-dec_onboarding:240212
    # update environment variable
    k8 set env deployment/wallet-onboarding \
      REGAPI_APPKEY=""
    ```
  
## Monitoring
  
#### Links
* Details: https://hackmd.io/VtIODwZhREOmM429FDCirA?view
* Tutorial: [Squadcast](https://www.squadcast.com/blog/infrastructure-monitoring-using-kube-prometheus-operator)

#### ToDos
* [ ] how to configure alerts
    * configure unavailability of node and notify in Slack
  
Access Dashboard:
* run `k8 --namespace monitoring port-forward svc/prometheus 9090`
* open [Prometheus](http://localhost:9090/)
