# Portainer CE in k3s mit der Datenbank von appsrv08 (Portainer EE 2.45.1). Läuft auf ops: # ansible-playbook playbooks/portainer-k3s.yml # # Voraussetzung: Sicherung des Docker-Volumes portainer_data von appsrv08 als /root/.config/knaster/portainer_data.tgz # (Inhalt: data/portainer.db, data/portainer.key, …; erzeugt mit # docker run --rm -v portainer_data:/data:ro alpine tar -C / -czf - data > portainer_data.tgz ). # Beim ersten Lauf wird das Volume befüllt und die Datenbank mit dem offiziellen Werkzeug der EE # (`--rollback-to-ce`) auf Community Edition zurückgesetzt; danach bleibt der Datenstand im Cluster. # Benutzer, API-Schlüssel und Umgebungen bleiben erhalten (getestet 15.10.2026). Die Umgebung 4 (Docker-Socket # von appsrv08) funktioniert im Cluster nicht mehr; appsrv08 wird als Agent-Umgebung neu angebunden. - name: Portainer CE nach k3s hosts: localhost connection: local gather_facts: false vars: manifest: "{{ playbook_dir }}/../../../../deploy/k8s/portainer/portainer.yaml" backup: /root/.config/knaster/portainer_data.tgz ns: portainer-ce version: 2.45.1 tasks: - name: Namespace, PVC, RBAC (noch ohne Deployment, damit das Volume zuerst befüllt wird) ansible.builtin.shell: > set -o pipefail; python3 -c "import sys,yaml; docs=[d for d in yaml.safe_load_all(open('{{ manifest }}')) if d and d['kind']!='Deployment' and d['kind']!='Service']; yaml.safe_dump_all(docs, sys.stdout)" | kubectl apply -f - args: {executable: /bin/bash} register: base changed_when: "'created' in base.stdout" - name: Ist das Volume schon befüllt? ansible.builtin.command: kubectl -n {{ ns }} get configmap portainer-seeded register: seeded failed_when: false changed_when: false - name: Sicherung vorhanden? ansible.builtin.stat: path: "{{ backup }}" register: bk when: seeded.rc != 0 failed_when: not bk.stat.exists - name: Volume aus der Sicherung befüllen (Hilfs-Pod auf dem Knoten k3s) ansible.builtin.shell: | set -e kubectl -n {{ ns }} delete pod seed --ignore-not-found >/dev/null cat </dev/null apiVersion: v1 kind: Pod metadata: {name: seed, namespace: {{ ns }}} spec: nodeSelector: {knaster.role: control} restartPolicy: Never containers: - name: seed image: alpine:3.20 command: ["sleep", "600"] volumeMounts: [{name: data, mountPath: /data}] volumes: [{name: data, persistentVolumeClaim: {claimName: portainer-data}}] EOF kubectl -n {{ ns }} wait --for=condition=Ready pod/seed --timeout=180s >/dev/null kubectl -n {{ ns }} cp {{ backup }} seed:/tmp/pd.tgz kubectl -n {{ ns }} exec seed -- sh -c "cd /tmp && tar xzf pd.tgz && cp -a data/. /data/ && test -s /data/portainer.db" kubectl -n {{ ns }} delete pod seed --wait=true >/dev/null when: seeded.rc != 0 - name: Datenbank auf Community Edition zurücksetzen (offizielles Werkzeug der EE, fragt per stdin) ansible.builtin.shell: | set -e kubectl -n {{ ns }} delete pod rollback --ignore-not-found >/dev/null echo y | kubectl -n {{ ns }} run rollback -i --restart=Never --image=portainer/portainer-ee:{{ version }} \ --overrides='{"spec":{"nodeSelector":{"knaster.role":"control"},"containers":[{"name":"rollback","image":"portainer/portainer-ee:{{ version }}","args":["--rollback-to-ce"],"stdin":true,"volumeMounts":[{"name":"data","mountPath":"/data"}]}],"volumes":[{"name":"data","persistentVolumeClaim":{"claimName":"portainer-data"}}]}}' >/dev/null 2>&1 || true sleep 5 kubectl -n {{ ns }} logs rollback | grep -q "rolled back to CE Edition" kubectl -n {{ ns }} delete pod rollback --wait=false >/dev/null kubectl -n {{ ns }} create configmap portainer-seeded --from-literal=source={{ backup | basename }} --from-literal=date="$(date -Is)" when: seeded.rc != 0 - name: Deployment und Service ansible.builtin.command: kubectl apply -f {{ manifest }} register: dep changed_when: "'configured' in dep.stdout or 'created' in dep.stdout" - name: Warten, bis bereit ansible.builtin.command: kubectl -n {{ ns }} rollout status deployment/portainer --timeout=300s changed_when: false - name: Version über NodePort (Knoten k3s, ohne Anmeldung) ansible.builtin.uri: url: "https://{{ hostvars['k3s'].ansible_host }}:30779/api/system/status" validate_certs: false return_content: true register: ver until: ver.status == 200 retries: 6 delay: 5 - name: Edition aus dem Startprotokoll (CE-Binary meldet sich als portainer/portainer, EE als portainer-ee) ansible.builtin.shell: kubectl -n {{ ns }} logs deployment/portainer | grep -o 'image_tag=[^ ]*' | tail -1 register: tag changed_when: false - name: Ergebnis ansible.builtin.assert: that: - ver.json.Version == version - "'ee' not in tag.stdout" success_msg: "Portainer CE {{ ver.json.Version }} ({{ tag.stdout }}) unter https://{{ hostvars['k3s'].ansible_host }}:30779"