Hochleistung-GoLang-Applikationen mit OTC RDS Operator

Erklärungsbedürftige IT-Produkte oder abgefahrene Technologien werden in der Funktion etwas anschaulicher, wenn sie in der Praxis erprobt werden können. Hilfreich dazu ist eine Demo App.

Posted by eumel8 on September 17, 2022 · 8 mins read

Demonstration zur Einsatzweise von Kubernetes incl. Container, Pod, Deployment, Service und Ingress findet man zum Beispiel hier, mit externem Volume auch hier. Das Ergebnis kann im Browser von überall im Internet besichtigt werden.

Der OTC RDS Operator kann OTC RDS Datenbanken aus einem Kubernetes Cluster deployen. Mit einer Autopilot Funktion skaliert die Instanz eigenständig. Wie kann ich aber so eine Resource in einer Applikation einbinden? Dazu hat der Operator seine eigene Demo App. Die basiert auf den bisherigen Demo App Modellen mit Webapplikation und MySQL-Backend, hat aber, in Go geschrieben, noch einige Besonderheiten:

import (
	// ...
	"k8s.io/client-go/rest"
	rdsv1alpha1clientset "github.com/eumel8/otc-rds-operator/pkg/rds/v1alpha1/apis/clientset/versioned"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ...
	for {
		log.Println("LOOP: get rds " + rdsname)
	
	rds, err := rdsclientset.McspsV1alpha1().Rdss(namespace).Get(context.TODO(), rdsname, metav1.GetOptions{})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			log.Println("KRDS: " + err.Error())
			return nil, err
		}
		if rds.Status.Status == "ACTIVE" {
			log.Println("DB ACTIVE")
			for _, i := range *rds.Spec.Users {
				dbUser = i.Name
				dbPass = i.Password
				break
			}
			dbDriver := "mysql"
			dbHost := rds.Status.Ip
			dbPort := rds.Spec.Port
			dbName := rds.Spec.Databases[0]
			log.Println("DB CONNECT " + dbHost)
			db, err = sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp("+dbHost+":"+dbPort+")/"+dbName)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				log.Println("DBCONN: " + err.Error())
				return nil, err
			}
		}
		// ...
		time.Sleep(5 * time.Second)
	}

Die Demo App läuft im Cluster, hat also automatisch Zugriff auf die Kubernetes-API. In der Funktion zum Herstellen der Verbindung zur Datenbank, ruft sie diese auf und sucht in den RDS Resourcen nach der passenden Instanz im selben Namespace, wartet bis diese im Status “ACTIVE” ist, sofern sie zusammen mit der App gerade deployt wurde und greift sich dort die Credentials ab, die ueber das RDS-Objekt definiert wurden und erfährt Hostname/IP-Adresse, die die OTC für die RDS-Instanz vergeben wurde und fortan im Status des RDS-Objekts im Kubernetes-Cluster stehen. Voila, die Verbindung klappt, die App kann die Datenbank nutzen.

Was im Labor klappt, muss nicht unbedingt im Produktionseinsatz funktionieren. Es gibt die Go App Ddosify, mit der Lastszenarios für Webapplikatonen nachgestellt werden können. Das Programm gibt es auch als Onlinedienst, ist aber nach einer kurzen Testphase kostenpflichtig. Dafür läuft das Programm in vielen Standorten der Welt, von der man seinen Dienst testen kann. Wir können uns aber auch das Binary herunterladen und von unserer Wörkstation unsere Demo App testen:

$ ddosify -t https://oro-demoapp.mcsps.telekomcloud.com/insert -m POST -h "Content-Type: application/x-www-form-urlencoded" -b "name=value1&city=value2" -l incremental -n 200000 -d 1200 -T 1

# enspricht
curl -X POST https://oro-demoapp.mcsps.telekomcloud.com/insert
   -H "Content-Type: application/x-www-form-urlencoded" 
   -d "name=value1&city=value2"

200.000 neue Datenbankeinträge in 20 Minuten (damit nach 15 Minuten unser Autopilot des RDS Operator anspringt). Das hält unsere Labor-App nicht aus.

Erster Fehler: MySQL - too many connections. Lässt sich auf der normalen Datenbank über Variablen ändern:

mysql> set global max_connections = 2000;

Klappt in der OTC aber nicht und ist in der Parametergroup einzustellen. Default sind es 800 in der MySQL-Default-Parametergroup. Ist bischen umständlich in der OTC-Web-Console einzustellen. Weder der Operator noch der openstack-Client bieten derzeit eine passende Option. Danach gleich Datenbank rebooten.

Nächster Fehler: Oomkilled POD. Die spartanischen Limits der Labor-App sind erreicht. Diese kann man im Statefulset erhöhen:

$ kubectl -n rds1 edit statefulsets.apps demoapp
...
       resources:
          limits:
            cpu: "1"
            memory: 1280Mi
          requests:
            cpu: 100m
            memory: 128Mi

Nächster Fehler: too many open files. Vermutlich im Container, weil das Verzeichnis zum Ablegen der Bilder immer neu geladen wird bei jedem Zugriff. Besser ist also die Anwendung in die Breite zu skalieren:

$ kubectl -n rds1 scale --replicas=5 statefulsets.apps demoapp

Mit angehängtem Storage klappt das bloss bei Multi-Attach-fähigem oder RWX-Storage wie NFS-Client. Für Performance-Tests der Datenbank können wir auf den permanenten Bilderspeicher verzichten und das Verzeichnis in den RAM-Speicher des PODs legen, indem wir den PVC Eintrag ersetzen durch:

      volumes:
      - emptyDir:
          medium: Memory
        name: demoapp-volume

Die Demo App läuft damit schon recht gut, die RDS wird vielleicht tatsächlich skaliert:

Events:
  Type    Reason  Age                From              Message
  ----    ------  ----               ----              -------
  Normal  Update  34m (x52 over 9h)  otc-rds-operator  This instance set autopilot.
  Normal  Update  29m (x2 over 29m)  otc-rds-operator  This instance is scaling to rds.mysql.s1.medium.ha

Allerdings gibt es noch genug Stellschrauben an den Komponenten, die bei unserer Demo App beteiligt sind: Loadbalancer, Ingress, Worker Nodes. Ddosify zählt die Fehlerarten und gibt am Ende seines Werkens einen Bericht.

RESULT
-------------------------------------
Success Count:    466   (46%)
Failed Count:     534   (54%)

Durations (Avg):
  DNS                  :0.0018s
  Connection           :0.0404s
  TLS                  :0.0912s
  Request Write        :0.0001s
  Server Processing    :1.0341s
  Response Read        :0.4948s
  Total                :1.6625s

Status Code (Message) :Count
  200 (OK)                       :194
  500 (Internal Server Error)    :272

Error Distribution (Count:Reason):
  534     :connection timeout

Damit kann man dann die Installation untersuchen und die Qualität seines Dienstes verbessern.

Happy Testing