­

Kubernetes: Autoimageupdate mit keel.sh mit private Registry mit custom Root CA

ConfigMap für keel.sh für die custom Root CA erzeugen

apiVersion: v1
kind: ConfigMap
metadata:
  name: ca-gallauner
  namespace: "keel"
data:
   ca-gallauner.pem: |
     -----BEGIN CERTIFICATE-----
     ....
     -----END CERTIFICATE-----

keel.sh via YAML installieren

  • Basic Auth für Dashboard (PortForward 9300 von Pod)
  • ConfigMap für CA mounten
      containers:
        - name: keel
          image: "keelhq/keel:latest"
          imagePullPolicy: Always
          command: ["/bin/keel"]
          env:
            - name: NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            # Basic auth (to enable UI/API)
            - name: BASIC_AUTH_USER
              value: "admin"
            - name: BASIC_AUTH_PASSWORD
              value: "admin"
            # Enable insecure registries
            - name: INSECURE_REGISTRY
              value: "false"
          ports:
            - containerPort: 9300
          livenessProbe:
            httpGet:
              path: /healthz
              port: 9300
            initialDelaySeconds: 30
            timeoutSeconds: 10
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 50m
              memory: 64Mi
          volumeMounts:
          - name: ca-gallauner
            mountPath: /etc/ssl/certs/ca-gallauner.pem
            subPath: ca-gallauner.pem
            readOnly: false
      volumes:
      - name: ca-gallauner
        configMap:
          name: ca-gallauner

keel.sh Annotations im Zieldeployment einfügen

    metadata:
      labels:
        app: homer
      annotations:
        keel.sh/policy: all
        keel.sh/trigger: poll
        keel.sh/pollSchedule: "@every 1m"
        keel.sh/approvals: "0"

Private Registry mit Custom Root CA in Kubernetes benutzen

In Kubernetes ImagePullSecret erzeugen:

kubectl create secret docker-registry myregistry --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

Im Pod/Deployment imagePullSecret hinzufügen

    spec:
      containers:
      - name: nginx
        image: myregistry/gallauner/homer:latest
      ports:
        - containerPort: 443
      imagePullSecrets:
      - name: myregistry

Gitea+Actions zum vollelektrischen Containerupdate hinter Proxy

Gitea von Binary installieren: https://docs.gitea.com/installation/install-from-binary

  • Debian nach eigenem Standard
  • git installieren
  • User “git” erzeugen
  • Verzeichnisse unter /var/lib/gitea erzeugen und schützen
  • /etc/gitea erzeugen und schützen
  • passendes gitea von https://dl.gitea.com/gitea/ holen, auf /usr/local/bin/ kopieren
  • systemd Unit erzeugen – https://docs.gitea.com/installation/linux-service
  • Environment=HTTP_PROXY=xxxx einfügen
  • Environment=HTTPS_PROXY=xxxx einfügen
  • Environment=NO_PROXY=xxxx einfügen

Gitea Actions und Migrations (=Replica) über Proxy erlauben (/etc/gitea/app.ini):

[proxy]
PROXY_ENABLED = true
PROXY_URL =

[migrations]
ALLOW_LOCALNETWORKS = true
ALLOWED_DOMAINS = gitea.com

[actions]
ENABLED=true
DEFAULT_ACTIONS_URL=self
STORAGE_TYPE=local
  • HTTP_PORT, ROOT_URL, CERT_FILE, PROTOCOL für HTTPS

Notwendige actions von Gitea.com replizieren (leicht modifizierte Namen damit alles unter “actions” liegt):

  • Gitea Organisation “actions” erzeugen
  • Migration von https://gitea.com/docker/login-action auf docker-login in actions
  • Migration von https://gitea.com/docker/setup-buildx-action auf docker-setup-buildx in actions
  • Migration von https://gitea.com/docker/build-push-action auf docker-build-push in actions
  • Migration von https://gitea.com/actions/checkout auf checkout in actions

act_runner (ausführendes Organ) konfigurieren (https://docs.gitea.com/usage/actions/quickstart)

  • passendes Binary von https://dl.gitea.com/act_runner/ holen
  • auf passendem Level (Instance, Organization, Repository) Token erzeugen und Runner registrieren
  • –labels linux_x64 beim Register angeben (sollte im config.yaml unter runner:/labels stehen)
  • config erzeugen (./act_runner generate-config > config.yaml)
  • unter “runner:” Block “envs:” mit HTTP_PROXY: xxxx, HTTPS_PROXY: xxxx, NO_PROXY: xxxx einfügen
  • mit config starten: ./act_runner daemon -c ./config.yaml

Action secret GIT_ACCESS_TOKEN erzeugen

  • https://gitea-host/api/swagger#/user/userCreateToken
  • nur für single User sinnvoll weil Token vom User sein muss der git push durchführt

Action secrets für Webserverzertifikat erzeugen

  • CERT_CRT mit public key
  • CERT_KEY mit private key

Im Repository .gitea/workflows/docker.yaml erzeugen:

jobs:
  Homer-Build-and-Push-Docker-Image:
    runs-on: linux_x64
    steps:
      - run: echo "${{ gitea.actor }} did ${{ gitea.event_name }} to ${{ gitea.repository }}, branch ${{ gitea.ref }}"
      - name: Check out repository code
        uses: actions/checkout@v3
        with:
          username: ${{ gitea.actor }}
          password: ${{ secrets.GIT_ACCESS_TOKEN }}
      - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: ls ${{ gitea.workspace }}
      - name: Login docker
        uses: actions/docker-login@v2
        with:
          registry: ${{ env.DOCKER_REGISTRY }}
          username: ${{ gitea.actor }}
          password: ${{ secrets.GIT_ACCESS_TOKEN }}
      - name: Setup docker buildx
        uses: actions/docker-setup-buildx@v2
        env:
          HTTP_PROXY: ${{ env.HTTP_PROXY }}
          HTTPS_PROXY: ${{ env.HTTPS_PROXY }}
          NO_PROXY: ${{ env.NO_PROXY }}
        with:
           driver-opts: |
             env.http_proxy=${{ env.HTTP_PROXY }}
             env.https_proxy=${{ env.HTTPS_PROXY }}
             "env.no_proxy='${{ env.NO_PROXY }}'" 
           config-inline: |
             [registry."${{ env.DOCKER_REGISTRY }}"]
               ca=["/etc/docker/certs.d/${{ env.DOCKER_REGISTRY }}/GallaunerCA.crt"]             
      - name: Build and push docker image
        uses: actions/docker-build-push@v4
        env:
          ACTIONS_RUNTIME_TOKEN: ''
        with:
          context: .
          push: true
          tags: ${{ env.DOCKER_REGISTRY }}/gallauner/homer
          secrets: |
             "CERT_CRT=${{ secrets.CERT_CRT }}"
             "CERT_KEY=${{ secrets.CERT_KEY }}"
      - run: echo "🍏 This job's status is ${{ job.status }}."
  • runs-on: linux_x64 entspricht dem Label vom act_runner
  • Proxywerte aus dem Environment werden für pull von Basisimages benötigt
  • Custom CA der privaten Registry wird im config-inline von docker-setup-build eingefügt
  • Zertifikat für Webserver wird über secrets von docker-build-push eingefügt (siehe Dockerfile)

Dockerfile

FROM nginx:alpine
COPY homer /usr/share/nginx/html/
RUN --mount=type=secret,id=CERT_CRT \
   cp /run/secrets/CERT_CRT /etc/ssl/certs/homer.crt
RUN --mount=type=secret,id=CERT_KEY \
   cp /run/secrets/CERT_KEY /etc/ssl/private/homer.key
COPY <<EOF /etc/nginx/conf.d/default.conf
server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /etc/ssl/certs/homer.crt;
    ssl_certificate_key /etc/ssl/private/homer.key;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
EOF
  • durch die “secrets” in der Action können die Secrets gemounted und kopiert werden

Zertifikat mit easy-rsa erzeugen

Private key erzeugen

openssl genrsa -out myserver.key 4096

Zertifikatsrequest erzeugen

openssl req -new -key myserver.key -out myserver.req -subj "/C=AT/L=Vienna/O=Gallauner/CN=myserver.fqdn"

Request in easy-rsa importieren

./easyrsa import-req myserver.req myserver

Request in easy-rsa signieren, optional mit SAN(s) – CN wird per default als SAN eingetragen wenn –san nicht extra angegeben ist:

./easyrsa --san=DNS:myserver.fqdn,DNS:alternatename.fqdn sign-req server myserver

Ergebnis in

./pki/myserver.crt

EU Lebensmittelcodes

Auf Lebensmitteln mit Inhaltsbestandteilen aus tierischer Herkunft muss ja der zuletzt beteiligte Betrieb als Code abgedruckt werden – in DE gibts da öffentliche Datenbanken mit Suchfunktion in AT ist das ein wenig schwieriger (ich hab zumindestens nix gefunden) – hier gibts aber alle Codes in PDF-Listenform:

https://vis.statistik.at/fileadmin/ovis/pdf/index.html

Die erste Stelle der Nummer zeigt übrigens das Bundesland:

  • 1 = Burgenland
  • 2 = Kärnten
  • 3 = Niederösterreich
  • 4 = Oberösterreich
  • 5 = Salzburg
  • 6 = Steiermark
  • 7 = Tirol
  • 8 = Vorarlberg
  • 9 = Wien

OpenShift Persistent Volume im Ceph FS finden

Hatte die Herausforderung dass ich PVC erweitert habe (Größe verdoppelt) und das Ding Events vom Typ

NodeExpandVolume.NodeExpandVolume failed for volume "pvc-....guid...." : Expander.NodeExpand found CSI plugin kubernetes.io/csi/openshift-storage.cephfs.csi.ceph.com to not support node expansion

gespuckt hat obwohl das Webinterface überall die neue Größe angezeigt hat. Wollte schauen wie Ceph das sieht.

Schritt 1: Ceph Tools Pod erzeugen

oc patch OCSInitialization ocsinit -n openshift-storage --type json --patch  '[{ "op": "replace", "path": "/spec/enableCephTools", "value": true }]'

erzeugt im Namespace openshift-storage einen Pod mit Namen “rook-ceph-tools-*”, dort Shell öffnen:

oc rsh -n openshift-storage rook-ceph-tools-tralala-dada

Schritt 2: Name vom Filesystem/Volume rausfinden (zu finden auch in den Specs vom PV):

ceph fs ls    (oder ceph fs volume ls)

Schritt 3: SubVolumeGroup rausfinden

ceph fs subvolumegroup ls <Name aus Schritt 2>

Schritt 4: Volumes in der SubVolumeGroup auflisten

ceph fs subvolume ls <Name aus Schritt 2> --group_name <Name aus Schritt 3>

Schritt 5: Infos zu Volume/PV anzeigen (Name des Volumes aus YAML des PV – “subvolumeName”)

ceph fs subvolume info <Name aus Schritt 2> <Name aus Schritt 4> --group_name <Name aus Schritt 3>

Red Hat OpenShift Image Registry not configured

Wer einfach zum spielen einen OpenShift Cluster (Assisted über Web mit je einer Zusatzdisk (200 GB pro Knoten) damit OpenShift Data Foundation einen StorageCluster mit Ceph erzeugt) erzeugt hat zwar am Ende einen Cluster aber keine (funktionierende) Image Registry (weil die kein Storage hat), will man eine Application dann deployen schlägt man sehr schnell (in meinem Fall beim Build) bei “Image Registry not configured” (oder so ähnlich, in irgendwelchen Events) auf.

Habs vermutlich nur überlesen aber man muss der Registry Storage besorgen (PVC, oc apply -f meineregistry.yml) und mitteilen (Cluster Config):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: registry-storage-pvc
namespace: openshift-image-registry
spec:
accessModes:
  - ReadWriteMany
resources:
  requests:
     storage: 100Gi
storageClassName: ocs-storagecluster-cephfs
oc patch config.image/cluster -p '{"spec":{"managementState":"Managed","replicas":2,"storage":{"managementState":"Unmanaged","pvc":{"claim":"registry-storage-pvc"}}}}' --type=merge

Quelle (für 4.13, Version auswählbar): https://docs.openshift.com/container-platform/4.13/registry/configuring_registry_storage/configuring-registry-storage-rhodf.html#registry-configuring-registry-storage-rhodf-cephfs_configuring-registry-storage-rhodf

Superbilliger hochverfügbarer Ingress Load Balancer

Hintergrund: Kubernetes, mehrere Ingresses, 3 Ingressknoten (ingress1-3.fun), ein Ingress/Service kann mit Prefixen nicht umgehen (absoluter Redirect) – daher verschiedene externe Namen und ein externer Load Balancer notwendig (webservice1+2.fun).

Superbillige Lösung:

  • 2 VMs (1 vCPU, 1 GB RAM, 10 GB Disk)
  • je eine für Zielclients erreichbare Adresse für webservice1+2.fun
  • HAProxy mit zwei frontends für webservice1+2.fun
  • keepalived mit VRRP Failover (die zwei öffentlichen Adressen)
  • Ingress Rules gehen auf die Namen, also http://webserviceX.fun/ => pod_für_webserviceX:port_für_webserviceX
/etc/haproxy/haproxy.cfg:

frontend webservice1
    bind webservice1.fun:80
    mode tcp
    default_backend backendnodes

frontend webservice2
    bind webservice2.fun:80
    mode tcp
    default_backend backendnodes

backend backendnodes
    balance source
    server node1 ingress1.fun:80 check
    server node2 ingress2.fun:80 check
    server node3 ingress3.fun:80 check

/etc/keepalived/keepalived.conf:

vrrp_instance VI_1 {
    state MASTER    # BACKUP am zweiten Knoten
    interface eth0
    virtual_router_id 51
    priority 255    # 254 am zweiten Knoten
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 12345
    }
    virtual_ipaddress {
        1.2.3.1/24          # öffentliche Adresse webservice1.fun
        1.2.3.2/24          # öffentliche Adresse webservice2.fun
    }

    notify_master "systemctl restart haproxy"
}

Durch das “notify_master” wird nach Failover der Adresse der HAProxy am jeweiligen Knoten neu gestartet und kann sich auf die virtuellen Adressen binden.

Mit “vrrp_track_process”/”track_process” oder “vrrp_script”/”track_script” könnte man noch Healthprobes (Existenz eines Prozesses – haproxy in diesem Fall oder ganzes Healthcheck Script) einführen – in der o.a. Konfiguration spricht keepalived nur an wenn VRRP Heartbeats vom MASTER nicht mehr ankommen.

Ingress spec (Controller=ingress-nginx):

spec:
  ingressClassName: nginx
  rules:
    - host: webservice1.fun
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: webservice1-server
                port:
                  number: 80

Das Ganze macht mit HTTPS dann naturgemäß 200x mal mehr Spaß 😀

WinRT mit C# in WinForms (.NET Framework)

  • Normales WinForms Projekt erzeugen
  • NuGet Package Management Format auswählbar machen:
    • Tools / Options / NuGet Package Manager / Allow format selection on first package install
  • NuGet Microsoft.Windows.SDK.Contracts mit der Version passend zur gewünschten Mindest-Windows-Version installieren
    • wenn die Auswahl PackageReference auswählen
  • Fröhlich WinRT Methoden verwenden, Beispiel (völlig non-async mal zur Abwechslung):
public static void SetLockscreen(string sUserPaper)
{
   Windows.Storage.StorageFile oUserPaper;

   oUserPaper=Windows.Storage.StorageFile.GetFileFromPathAsync(sUserPaper).AsTask().GetAwaiter().GetResult();
   Windows.System.UserProfile.LockScreen.SetImageFileAsync(oUserPaper).AsTask().Wait();
}